/******************************************************************************* * Copyright (c) 2003, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Bug 349224 Navigator content provider "appearsBefore" creates hard reference to named id - paul.fullbright@oracle.com * C. Sean Young <csyoung@google.com> - Bug 436645 *******************************************************************************/ package org.eclipse.ui.internal.navigator.extensions; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.WeakHashMap; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.internal.navigator.NavigatorPlugin; import org.eclipse.ui.internal.navigator.NavigatorSafeRunnable; import org.eclipse.ui.internal.navigator.Policy; import org.eclipse.ui.internal.navigator.VisibilityAssistant; import org.eclipse.ui.navigator.INavigatorContentDescriptor; import org.eclipse.ui.navigator.OverridePolicy; import org.eclipse.ui.plugin.AbstractUIPlugin; /** * @since 3.2 */ public class NavigatorContentDescriptorManager { private static final NavigatorContentDescriptorManager INSTANCE = new NavigatorContentDescriptorManager(); private final Map<String, NavigatorContentDescriptor> firstClassDescriptorsMap = new HashMap<>(); private final Map<String, NavigatorContentDescriptor> allDescriptors = new HashMap<>(); private final Map<VisibilityAssistant, EvaluationCache> cachedTriggerPointEvaluations = new WeakHashMap<>(); private final Map<VisibilityAssistant, EvaluationCache> cachedPossibleChildrenEvaluations = new WeakHashMap<>(); private ImageRegistry imageRegistry; private final Set<NavigatorContentDescriptor> overridingDescriptors = new HashSet<>(); private final Set<NavigatorContentDescriptor> saveablesProviderDescriptors = new HashSet<>(); private final Set<NavigatorContentDescriptor> sortOnlyDescriptors = new HashSet<>(); private final Set<NavigatorContentDescriptor> firstClassDescriptorsSet = new HashSet<>(); /** * @return the singleton instance of the manager */ public static NavigatorContentDescriptorManager getInstance() { return INSTANCE; } private NavigatorContentDescriptorManager() { new NavigatorContentDescriptorRegistry().readRegistry(); } /** * * @return Returns all content descriptor(s). */ public NavigatorContentDescriptor[] getAllContentDescriptors() { NavigatorContentDescriptor[] finalDescriptors = new NavigatorContentDescriptor[allDescriptors .size()]; finalDescriptors = allDescriptors.values().toArray(finalDescriptors); Arrays.sort(finalDescriptors, ExtensionSequenceNumberComparator.INSTANCE); return finalDescriptors; } /** * * @return Returns all content descriptors that provide saveables. */ public NavigatorContentDescriptor[] getContentDescriptorsWithSaveables() { NavigatorContentDescriptor[] finalDescriptors = new NavigatorContentDescriptor[saveablesProviderDescriptors .size()]; saveablesProviderDescriptors.toArray(finalDescriptors); Arrays.sort(finalDescriptors, ExtensionSequenceNumberComparator.INSTANCE); return finalDescriptors; } /** * * @return Returns all content descriptors that are sort only */ public NavigatorContentDescriptor[] getSortOnlyContentDescriptors() { NavigatorContentDescriptor[] finalDescriptors = new NavigatorContentDescriptor[sortOnlyDescriptors .size()]; sortOnlyDescriptors.toArray(finalDescriptors); Arrays.sort(finalDescriptors, ExtensionSequenceNumberComparator.INSTANCE); return finalDescriptors; } /** * * Returns all content descriptor(s) which enable for the given element. * * @param anElement * the element to return the best content descriptor for * * @param aVisibilityAssistant * The relevant viewer assistant; used to filter out unbound * content descriptors. * @param considerOverrides * @return the best content descriptor for the given element. */ public Set<NavigatorContentDescriptor> findDescriptorsForTriggerPoint(Object anElement, VisibilityAssistant aVisibilityAssistant, boolean considerOverrides) { return findDescriptors(anElement, cachedTriggerPointEvaluations, aVisibilityAssistant, considerOverrides, !POSSIBLE_CHILD); } /** * * Returns all content descriptor(s) which enable for the given element. * * @param anElement * the element to return the best content descriptor for * * @param aVisibilityAssistant * The relevant viewer assistant; used to filter out unbound * content descriptors. * @param toComputeOverrides * @return the best content descriptor for the given element. */ public Set<NavigatorContentDescriptor> findDescriptorsForPossibleChild(Object anElement, VisibilityAssistant aVisibilityAssistant, boolean toComputeOverrides) { return findDescriptors(anElement, cachedPossibleChildrenEvaluations, aVisibilityAssistant, toComputeOverrides, POSSIBLE_CHILD); } private static final boolean POSSIBLE_CHILD = true; private Set<NavigatorContentDescriptor> findDescriptors(Object anElement, Map<VisibilityAssistant, EvaluationCache> cachedEvaluations, VisibilityAssistant aVisibilityAssistant, boolean considerOverrides, boolean possibleChild) { EvaluationCache cache = getEvaluationCache(cachedEvaluations, aVisibilityAssistant); Set<NavigatorContentDescriptor> descriptors = new TreeSet<NavigatorContentDescriptor>(ExtensionSequenceNumberComparator.INSTANCE); NavigatorContentDescriptor[] cachedDescriptors = null; if ((cachedDescriptors = cache.getDescriptors(anElement, considerOverrides)) != null) { descriptors.addAll(Arrays.asList(cachedDescriptors)); if (Policy.DEBUG_RESOLUTION) { System.out.println("Find descriptors for : " + Policy.getObjectString(anElement) + //$NON-NLS-1$ (considerOverrides ? " (with overrides)" : "") + " (cached): " + descriptors); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } return descriptors; } if (considerOverrides) { addDescriptorsConsideringOverrides(anElement, firstClassDescriptorsSet, aVisibilityAssistant, descriptors, possibleChild); } else { /* Find other ContentProviders which enable for this object */ for (NavigatorContentDescriptor descriptor : firstClassDescriptorsSet) { if (aVisibilityAssistant.isActive(descriptor) && aVisibilityAssistant.isVisible(descriptor) && (possibleChild ? descriptor.isPossibleChild(anElement) : descriptor.isTriggerPoint(anElement))) { descriptors.add(descriptor); } } } if (Policy.DEBUG_RESOLUTION) { System.out.println("Find descriptors for: " + Policy.getObjectString(anElement) + //$NON-NLS-1$ (considerOverrides ? " (with overrides)" : "") + ": " + descriptors); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } cache.setDescriptors(anElement, descriptors.toArray(new NavigatorContentDescriptor[descriptors.size()]), considerOverrides); return descriptors; } private EvaluationCache getEvaluationCache(Map<VisibilityAssistant, EvaluationCache> anEvaluationMap, VisibilityAssistant aVisibilityAssistant) { EvaluationCache c = anEvaluationMap.get(aVisibilityAssistant); if (c == null) { anEvaluationMap.put(aVisibilityAssistant, c = new EvaluationCache(aVisibilityAssistant)); } return c; } private boolean addDescriptorsConsideringOverrides(Object anElement, Set<NavigatorContentDescriptor> theChildDescriptors, VisibilityAssistant aVisibilityAssistant, Set<NavigatorContentDescriptor> theFoundDescriptors, boolean possibleChild) { int initialSize = theFoundDescriptors.size(); NavigatorContentDescriptor descriptor; /* Find other ContentProviders which enable for this object */ for (Iterator<NavigatorContentDescriptor> contentDescriptorsItr = theChildDescriptors.iterator(); contentDescriptorsItr .hasNext();) { descriptor = contentDescriptorsItr .next(); boolean isApplicable = aVisibilityAssistant.isActive(descriptor) && aVisibilityAssistant.isVisible(descriptor) && (possibleChild ? descriptor.isPossibleChild(anElement) : descriptor.isTriggerPoint(anElement)); if (descriptor.hasOverridingExtensions()) { boolean isOverridden; Set<NavigatorContentDescriptor> overridingDescriptors = new TreeSet<NavigatorContentDescriptor>(ExtensionSequenceNumberComparator.INSTANCE); isOverridden = addDescriptorsConsideringOverrides(anElement, descriptor.getOverriddingExtensions(), aVisibilityAssistant, overridingDescriptors, possibleChild); if (!isOverridden && isApplicable) { theFoundDescriptors.add(descriptor); } else if (isOverridden) { theFoundDescriptors.addAll(overridingDescriptors); } } else if (isApplicable) { theFoundDescriptors.add(descriptor); } } return initialSize < theFoundDescriptors.size(); } /** * Returns the navigator content descriptor with the given id. * * @param id * The id of the content descriptor that should be returned * @return The content descriptor of the given id */ public NavigatorContentDescriptor getContentDescriptor(String id) { return allDescriptors.get(id); } /** * * @param descriptorId * The unique id of a particular descriptor * @return The name (value of the 'name' attribute) of the given descriptor */ public String getText(String descriptorId) { INavigatorContentDescriptor descriptor = getContentDescriptor(descriptorId); if (descriptor != null) { return descriptor.getName(); } return descriptorId; } /** * * @param descriptorId * The unique id of a particular descriptor * @return The image (corresponding to the value of the 'icon' attribute) of * the given descriptor */ public Image getImage(String descriptorId) { return retrieveAndStoreImage(descriptorId); } protected Image retrieveAndStoreImage(String descriptorId) { NavigatorContentDescriptor contentDescriptor = getContentDescriptor(descriptorId); Image image = null; if (contentDescriptor != null) { String iconPath = contentDescriptor.getIcon(); if (iconPath != null) { String prefix = contentDescriptor.getId() == null ? "" : contentDescriptor.getId(); //$NON-NLS-1$ String iconKey = prefix + "::" + iconPath; //$NON-NLS-1$ image = getImageRegistry().get(iconKey); if (image == null || image.isDisposed()) { ImageDescriptor imageDescriptor = AbstractUIPlugin .imageDescriptorFromPlugin(contentDescriptor .getContribution().getPluginId(), iconPath); if (imageDescriptor != null) { image = imageDescriptor.createImage(); if (image != null) { getImageRegistry().put(iconKey, image); } } } } } return image; } /** * Clears all cached information. */ public void clearCache() { for (EvaluationCache cache : cachedPossibleChildrenEvaluations.values()) { cache.clear(); } for (EvaluationCache cache : cachedTriggerPointEvaluations.values()) { cache.clear(); } } /** * @param desc */ private void addNavigatorContentDescriptor(NavigatorContentDescriptor desc) { if (desc == null) { return; } synchronized (firstClassDescriptorsMap) { if (firstClassDescriptorsMap.containsKey(desc.getId())) { NavigatorPlugin .logError( 0, "An extension already exists with id \"" + desc.getId() + "\".", null); //$NON-NLS-1$ //$NON-NLS-2$ } else { if (desc.getSuppressedExtensionId() == null) { firstClassDescriptorsMap.put(desc.getId(), desc); firstClassDescriptorsSet.add(desc); if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println("First class descriptor: " + desc); //$NON-NLS-1$ } } else { overridingDescriptors.add(desc); if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println("Overriding descriptor: " + desc); //$NON-NLS-1$ } } allDescriptors.put(desc.getId(), desc); if (desc.hasSaveablesProvider()) { saveablesProviderDescriptors.add(desc); if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println("Saveables provider descriptor: " + desc); //$NON-NLS-1$ } } if (desc.isSortOnly()) { sortOnlyDescriptors.add(desc); if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println("SortOnly descriptor: " + desc); //$NON-NLS-1$ } } } } } /** * */ private void computeOverrides() { if (overridingDescriptors.size() > 0) { NavigatorContentDescriptor descriptor; NavigatorContentDescriptor overriddenDescriptor; for (Iterator<NavigatorContentDescriptor> overridingIterator = overridingDescriptors.iterator(); overridingIterator .hasNext();) { descriptor = overridingIterator .next(); overriddenDescriptor = allDescriptors .get(descriptor.getSuppressedExtensionId()); if (overriddenDescriptor != null) { if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println(descriptor + " overrides: " + overriddenDescriptor); //$NON-NLS-1$ } /* * add the descriptor as an overriding extension for its * suppressed extension */ overriddenDescriptor.getOverriddingExtensions().add( descriptor); descriptor.setOverriddenDescriptor(overriddenDescriptor); /* * the always policy implies this is also a top-level * extension */ if (descriptor.getOverridePolicy() == OverridePolicy.InvokeAlwaysRegardlessOfSuppressedExt) { if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println(descriptor + " is first class"); //$NON-NLS-1$ } firstClassDescriptorsMap.put(descriptor.getId(), descriptor); firstClassDescriptorsSet.add(descriptor); } } else { String message = "Invalid suppressedExtensionId \"" //$NON-NLS-1$ + descriptor.getSuppressedExtensionId() + "\" specified from \"" //$NON-NLS-1$ + descriptor.getId() + "\" in \"" + descriptor.getContribution() //$NON-NLS-1$ .getPluginId() + "\". No extension with matching id found."; //$NON-NLS-1$ if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println("Error: " + message); //$NON-NLS-1$ } NavigatorPlugin.logError(0, message, null); } } } } private int findId(List<NavigatorContentDescriptor> list, String id) { for (int i = 0, len = list.size(); i < len; i++) { NavigatorContentDescriptor desc = list.get(i); if (desc.getId().equals(id)) return i; } // Do not require content descriptor to exist in workspace NavigatorPlugin.log(IStatus.WARNING, 0, "Can't find Navigator Content Descriptor with id: " + id, null); //$NON-NLS-1$ return -1; } private void computeSequenceNumbers() { NavigatorContentDescriptor[] descs = getAllContentDescriptors(); LinkedList<NavigatorContentDescriptor> list = new LinkedList<NavigatorContentDescriptor>(); for (NavigatorContentDescriptor desc : descs) { list.add(desc); } boolean changed = true; while (changed) { changed = false; for (int i = 0, len = list.size(); i < len; i++) { NavigatorContentDescriptor desc = list.get(i); if (desc.getAppearsBeforeId() != null) { int beforeInd = findId(list, desc.getAppearsBeforeId()); if (beforeInd >= 0 && beforeInd < i) { list.add(beforeInd, desc); list.remove(i + 1); changed = true; } } } } for (int i = 0, len = list.size(); i < len; i++) { NavigatorContentDescriptor desc = list.get(i); desc.setSequenceNumber(i); if (Policy.DEBUG_EXTENSION_SETUP) { System.out.println("Descriptors by sequence: " + desc); //$NON-NLS-1$ } } } private ImageRegistry getImageRegistry() { if (imageRegistry == null) { imageRegistry = new ImageRegistry(); } return imageRegistry; } private class NavigatorContentDescriptorRegistry extends NavigatorContentRegistryReader { @Override public void readRegistry() { super.readRegistry(); computeSequenceNumbers(); computeOverrides(); } @Override protected boolean readElement(final IConfigurationElement anElement) { if (TAG_NAVIGATOR_CONTENT.equals(anElement.getName())) { SafeRunner.run(new NavigatorSafeRunnable(anElement) { @Override public void run() throws Exception { addNavigatorContentDescriptor(new NavigatorContentDescriptor(anElement)); } }); } return super.readElement(anElement); } } }