| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.texteditor.rulers; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import com.ibm.icu.text.MessageFormat; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtensionRegistry; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.InvalidRegistryObjectException; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| |
| import org.eclipse.ui.internal.texteditor.TextEditorPlugin; |
| import org.eclipse.ui.internal.texteditor.rulers.DAG; |
| import org.eclipse.ui.internal.texteditor.rulers.ExtensionPointHelper; |
| import org.eclipse.ui.internal.texteditor.rulers.RulerColumnMessages; |
| import org.eclipse.ui.internal.texteditor.rulers.RulerColumnPlacementConstraint; |
| |
| import org.eclipse.ui.texteditor.ConfigurationElementSorter; |
| |
| |
| /** |
| * A registry for all extensions to the |
| * <code>rulerColumns</code> extension point. |
| * |
| * @since 3.3 |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| */ |
| public final class RulerColumnRegistry { |
| |
| private static final String EXTENSION_POINT= "rulerColumns"; //$NON-NLS-1$ |
| private static final String QUALIFIED_EXTENSION_POINT= TextEditorPlugin.PLUGIN_ID + '.' + EXTENSION_POINT; |
| |
| /** The singleton instance. */ |
| private static RulerColumnRegistry fgSingleton= null; |
| |
| /** |
| * Returns the default computer registry. |
| * <p> |
| * TODO keep this or add some other singleton, e.g. TextEditorPlugin? |
| * </p> |
| * |
| * @return the singleton instance |
| */ |
| public static synchronized RulerColumnRegistry getDefault() { |
| if (fgSingleton == null) { |
| fgSingleton= new RulerColumnRegistry(); |
| } |
| |
| return fgSingleton; |
| } |
| |
| /** |
| * All descriptors. |
| */ |
| private List<RulerColumnDescriptor> fDescriptors= null; |
| /** |
| * All descriptors by id. |
| */ |
| private Map<String, RulerColumnDescriptor> fDescriptorMap= null; |
| |
| /** |
| * <code>true</code> if this registry has been loaded. |
| */ |
| private boolean fLoaded= false; |
| |
| /** |
| * Creates a new instance. |
| */ |
| RulerColumnRegistry() { |
| } |
| |
| /** |
| * Returns the list of {@link RulerColumnDescriptor}s describing all extensions to the |
| * <code>rulerColumns</code> extension point. The list's iterator traverses the descriptors in |
| * the ordering implied by the placement specifications of the contributions. |
| * <p> |
| * The returned list is unmodifiable and guaranteed to never change. Note that the set of |
| * descriptors may change over time due to dynamic plug-in removal or addition. |
| * </p> |
| * |
| * @return the sorted list of extensions to the <code>rulerColumns</code> extension point |
| */ |
| public List<RulerColumnDescriptor> getColumnDescriptors() { |
| ensureExtensionPointRead(); |
| return fDescriptors; |
| } |
| |
| /** |
| * Returns the {@link RulerColumnDescriptor} with the given identity, <code>null</code> if no |
| * such descriptor exists. |
| * |
| * @param id the identity of the ruler contribution as given in the extension point xml. |
| * @return the {@link RulerColumnDescriptor} with the given identity, <code>null</code> if no |
| * such descriptor exists |
| */ |
| public RulerColumnDescriptor getColumnDescriptor(String id) { |
| Assert.isLegal(id != null); |
| ensureExtensionPointRead(); |
| return fDescriptorMap.get(id); |
| } |
| |
| /** |
| * Ensures that the extensions are read and stored in |
| * <code>fDescriptorsByPartition</code>. |
| */ |
| private void ensureExtensionPointRead() { |
| boolean reload; |
| synchronized (this) { |
| reload= !fLoaded; |
| fLoaded= true; |
| } |
| if (reload) |
| reload(); |
| } |
| |
| /** |
| * Reloads the extensions to the extension point. |
| * <p> |
| * This method can be called more than once in order to reload from |
| * a changed extension registry. |
| * </p> |
| */ |
| public void reload() { |
| IExtensionRegistry registry= Platform.getExtensionRegistry(); |
| List<IConfigurationElement> elements= new ArrayList<>(Arrays.asList(registry.getConfigurationElementsFor(TextEditorPlugin.PLUGIN_ID, EXTENSION_POINT))); |
| |
| List<RulerColumnDescriptor> descriptors= new ArrayList<>(); |
| Map<String, RulerColumnDescriptor> descriptorMap= new HashMap<>(); |
| |
| for (Iterator<IConfigurationElement> iter= elements.iterator(); iter.hasNext();) { |
| IConfigurationElement element= iter.next(); |
| try { |
| RulerColumnDescriptor desc= new RulerColumnDescriptor(element, this); |
| String id= desc.getId(); |
| if (descriptorMap.containsKey(id)) { |
| noteDuplicateId(desc); |
| continue; |
| } |
| |
| descriptors.add(desc); |
| descriptorMap.put(id, desc); |
| } catch (InvalidRegistryObjectException x) { |
| /* |
| * Element is not valid any longer as the contributing plug-in was unloaded or for |
| * some other reason. Do not include the extension in the list and inform the user |
| * about it. |
| */ |
| noteInvalidExtension(element, x); |
| } catch (CoreException x) { |
| warnUser(x.getStatus()); |
| } |
| } |
| |
| sort(descriptors); |
| |
| synchronized (this) { |
| fDescriptors= Collections.unmodifiableList(descriptors); |
| fDescriptorMap= Collections.unmodifiableMap(descriptorMap); |
| } |
| } |
| |
| /** |
| * Sorts the column contributions. |
| * |
| * @param descriptors the descriptors to sort |
| */ |
| private void sort(List<RulerColumnDescriptor> descriptors) { |
| /* |
| * Topological sort of the DAG defined by the plug-in dependencies |
| * 1. TopoSort descriptors by plug-in dependency |
| * 2. Insert into Directed Acyclic Graph |
| * 3. TopoSort DAG: pick the source with the lowest gravity and remove from DAG |
| */ |
| ConfigurationElementSorter sorter= new ConfigurationElementSorter() { |
| @Override |
| public IConfigurationElement getConfigurationElement(Object object) { |
| return ((RulerColumnDescriptor) object).getConfigurationElement(); |
| } |
| }; |
| RulerColumnDescriptor[] array= descriptors.toArray(new RulerColumnDescriptor[descriptors.size()]); |
| sorter.sort(array); |
| |
| Map<String, RulerColumnDescriptor> descriptorsById= new HashMap<>(); |
| for (RulerColumnDescriptor desc : array) { |
| descriptorsById.put(desc.getId(), desc); |
| } |
| |
| DAG<RulerColumnDescriptor> dag= new DAG<>(); |
| for (RulerColumnDescriptor desc : array) { |
| dag.addVertex(desc); |
| Set<RulerColumnPlacementConstraint> before= desc.getPlacement().getConstraints(); |
| for (RulerColumnPlacementConstraint constraint : before) { |
| String id= constraint.getId(); |
| RulerColumnDescriptor target= descriptorsById.get(id); |
| if (target == null) { |
| noteUnknownTarget(desc, id); |
| } else { |
| boolean success; |
| if (constraint.isBefore()) |
| success= dag.addEdge(desc, target); |
| else |
| success= dag.addEdge(target, desc); |
| if (!success) |
| noteCycle(desc, target); |
| } |
| } |
| } |
| |
| Comparator<RulerColumnDescriptor> gravityComp = (o1, o2) -> { |
| float diff = o1.getPlacement().getGravity() - o2.getPlacement().getGravity(); |
| if (diff == 0) |
| return 0; |
| if (diff < 0) |
| return -1; |
| return 1; |
| }; |
| |
| /* Topological sort - always select the source with the least gravity */ |
| Set<RulerColumnDescriptor> toProcess= dag.getSources(); |
| int index= 0; |
| while (!toProcess.isEmpty()) { |
| RulerColumnDescriptor next= Collections.min(toProcess, gravityComp); |
| array[index]= next; |
| index++; |
| dag.removeVertex(next); |
| toProcess= dag.getSources(); |
| } |
| Assert.isTrue(index == array.length); |
| |
| ListIterator<RulerColumnDescriptor> it= descriptors.listIterator(); |
| for (int i= 0; i < index; i++) { |
| it.next(); |
| it.set(array[i]); |
| } |
| } |
| |
| private void noteInvalidExtension(IConfigurationElement element, InvalidRegistryObjectException x) { |
| String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_invalid_msg, ExtensionPointHelper.findId(element)); |
| warnUser(message, x); |
| } |
| |
| private void noteUnknownTarget(RulerColumnDescriptor desc, String referencedId) { |
| String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_unresolved_placement_msg, |
| QUALIFIED_EXTENSION_POINT, referencedId, desc.getName(), desc.getContributor()); |
| warnUser(message, null); |
| } |
| |
| private void noteCycle(RulerColumnDescriptor desc, RulerColumnDescriptor target) { |
| String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_cyclic_placement_msg, QUALIFIED_EXTENSION_POINT, |
| target.getName(), desc.getName(), desc.getContributor()); |
| warnUser(message, null); |
| } |
| |
| private void noteDuplicateId(RulerColumnDescriptor desc) { |
| String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_duplicate_id_msg, QUALIFIED_EXTENSION_POINT, |
| desc.getId(), desc.getContributor()); |
| warnUser(message, null); |
| } |
| |
| private void warnUser(String message, Exception exception) { |
| IStatus status= new Status(IStatus.WARNING, TextEditorPlugin.PLUGIN_ID, IStatus.OK, message, exception); |
| warnUser(status); |
| } |
| |
| private void warnUser(IStatus status) { |
| TextEditorPlugin.getDefault().getLog().log(status); |
| } |
| } |