blob: bd92717cf7a8b4a9172b1a705c8d9f94837d897d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 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
*******************************************************************************/
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 (element type:
* {@link RulerColumnDescriptor}).
*/
private List fDescriptors= null;
/**
* All descriptors by id (element type: {@link RulerColumnDescriptor}).
*/
private Map 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
* (element type: {@link RulerColumnDescriptor})
*/
public List 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 (RulerColumnDescriptor) 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 elements= new ArrayList(Arrays.asList(registry.getConfigurationElementsFor(TextEditorPlugin.PLUGIN_ID, EXTENSION_POINT)));
List descriptors= new ArrayList();
Map descriptorMap= new HashMap();
for (Iterator iter= elements.iterator(); iter.hasNext();) {
IConfigurationElement element= (IConfigurationElement) 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 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() {
public IConfigurationElement getConfigurationElement(Object object) {
return ((RulerColumnDescriptor) object).getConfigurationElement();
}
};
Object[] array= descriptors.toArray();
sorter.sort(array);
Map descriptorsById= new HashMap();
for (int i= 0; i < array.length; i++) {
RulerColumnDescriptor desc= (RulerColumnDescriptor) array[i];
descriptorsById.put(desc.getId(), desc);
}
DAG dag= new DAG();
for (int i= 0; i < array.length; i++) {
RulerColumnDescriptor desc= (RulerColumnDescriptor) array[i];
dag.addVertex(desc);
Set before= desc.getPlacement().getConstraints();
for (Iterator it= before.iterator(); it.hasNext();) {
RulerColumnPlacementConstraint constraint= (RulerColumnPlacementConstraint) it.next();
String id= constraint.getId();
RulerColumnDescriptor target= (RulerColumnDescriptor) 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 gravityComp= new Comparator() {
public int compare(Object o1, Object o2) {
float diff= ((RulerColumnDescriptor) o1).getPlacement().getGravity() - ((RulerColumnDescriptor) 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 toProcess= dag.getSources();
int index= 0;
while (!toProcess.isEmpty()) {
Object next= Collections.min(toProcess, gravityComp);
array[index]= next;
index++;
dag.removeVertex(next);
toProcess= dag.getSources();
}
Assert.isTrue(index == array.length);
ListIterator 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, new Object[] {ExtensionPointHelper.findId(element)});
warnUser(message, x);
}
private void noteUnknownTarget(RulerColumnDescriptor desc, String referencedId) {
String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_unresolved_placement_msg, new Object[] {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, new Object[] {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, new Object[] {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);
}
}