001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.texteditor.rulers;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.Collections;
015: import java.util.Comparator;
016: import java.util.HashMap;
017: import java.util.Iterator;
018: import java.util.List;
019: import java.util.ListIterator;
020: import java.util.Map;
021: import java.util.Set;
022:
023: import com.ibm.icu.text.MessageFormat;
024:
025: import org.eclipse.core.runtime.Assert;
026: import org.eclipse.core.runtime.IConfigurationElement;
027: import org.eclipse.core.runtime.IExtensionRegistry;
028: import org.eclipse.core.runtime.IStatus;
029: import org.eclipse.core.runtime.InvalidRegistryObjectException;
030: import org.eclipse.core.runtime.Platform;
031: import org.eclipse.core.runtime.Status;
032:
033: import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
034: import org.eclipse.ui.internal.texteditor.rulers.DAG;
035: import org.eclipse.ui.internal.texteditor.rulers.ExtensionPointHelper;
036: import org.eclipse.ui.internal.texteditor.rulers.RulerColumnMessages;
037: import org.eclipse.ui.internal.texteditor.rulers.RulerColumnPlacementConstraint;
038: import org.eclipse.ui.texteditor.ConfigurationElementSorter;
039:
040: /**
041: * A registry for all extensions to the
042: * <code>rulerColumns</code> extension point.
043: *
044: * @since 3.3
045: */
046: public final class RulerColumnRegistry {
047:
048: private static final String EXTENSION_POINT = "rulerColumns"; //$NON-NLS-1$
049: private static final String QUALIFIED_EXTENSION_POINT = TextEditorPlugin.PLUGIN_ID
050: + '.' + EXTENSION_POINT;
051:
052: /** The singleton instance. */
053: private static RulerColumnRegistry fgSingleton = null;
054:
055: /**
056: * Returns the default computer registry.
057: * <p>
058: * TODO keep this or add some other singleton, e.g. TextEditorPlugin?
059: * </p>
060: *
061: * @return the singleton instance
062: */
063: public static synchronized RulerColumnRegistry getDefault() {
064: if (fgSingleton == null) {
065: fgSingleton = new RulerColumnRegistry();
066: }
067:
068: return fgSingleton;
069: }
070:
071: /**
072: * All descriptors (element type:
073: * {@link RulerColumnDescriptor}).
074: */
075: private List fDescriptors = null;
076: /**
077: * All descriptors by id (element type: {@link RulerColumnDescriptor}).
078: */
079: private Map fDescriptorMap = null;
080:
081: /**
082: * <code>true</code> if this registry has been loaded.
083: */
084: private boolean fLoaded = false;
085:
086: /**
087: * Creates a new instance.
088: */
089: RulerColumnRegistry() {
090: }
091:
092: /**
093: * Returns the list of {@link RulerColumnDescriptor}s describing all extensions to the
094: * <code>rulerColumns</code> extension point. The list's iterator traverses the descriptors in
095: * the ordering implied by the placement specifications of the contributions.
096: * <p>
097: * The returned list is unmodifiable and guaranteed to never change. Note that the set of
098: * descriptors may change over time due to dynamic plug-in removal or addition.
099: * </p>
100: *
101: * @return the sorted list of extensions to the <code>rulerColumns</code> extension point
102: * (element type: {@link RulerColumnDescriptor})
103: */
104: public List getColumnDescriptors() {
105: ensureExtensionPointRead();
106: return fDescriptors;
107: }
108:
109: /**
110: * Returns the {@link RulerColumnDescriptor} with the given identity, <code>null</code> if no
111: * such descriptor exists.
112: *
113: * @param id the identity of the ruler contribution as given in the extension point xml.
114: * @return the {@link RulerColumnDescriptor} with the given identity, <code>null</code> if no
115: * such descriptor exists
116: */
117: public RulerColumnDescriptor getColumnDescriptor(String id) {
118: Assert.isLegal(id != null);
119: ensureExtensionPointRead();
120: return (RulerColumnDescriptor) fDescriptorMap.get(id);
121: }
122:
123: /**
124: * Ensures that the extensions are read and stored in
125: * <code>fDescriptorsByPartition</code>.
126: */
127: private void ensureExtensionPointRead() {
128: boolean reload;
129: synchronized (this ) {
130: reload = !fLoaded;
131: fLoaded = true;
132: }
133: if (reload)
134: reload();
135: }
136:
137: /**
138: * Reloads the extensions to the extension point.
139: * <p>
140: * This method can be called more than once in order to reload from
141: * a changed extension registry.
142: * </p>
143: */
144: public void reload() {
145: IExtensionRegistry registry = Platform.getExtensionRegistry();
146: List elements = new ArrayList(Arrays.asList(registry
147: .getConfigurationElementsFor(
148: TextEditorPlugin.PLUGIN_ID, EXTENSION_POINT)));
149:
150: List descriptors = new ArrayList();
151: Map descriptorMap = new HashMap();
152:
153: for (Iterator iter = elements.iterator(); iter.hasNext();) {
154: IConfigurationElement element = (IConfigurationElement) iter
155: .next();
156: try {
157: RulerColumnDescriptor desc = new RulerColumnDescriptor(
158: element, this );
159: String id = desc.getId();
160: if (descriptorMap.containsKey(id)) {
161: noteDuplicateId(desc);
162: continue;
163: }
164:
165: descriptors.add(desc);
166: descriptorMap.put(id, desc);
167: } catch (InvalidRegistryObjectException x) {
168: /*
169: * Element is not valid any longer as the contributing plug-in was unloaded or for
170: * some other reason. Do not include the extension in the list and inform the user
171: * about it.
172: */
173: noteInvalidExtension(element, x);
174: }
175: }
176:
177: sort(descriptors);
178:
179: synchronized (this ) {
180: fDescriptors = Collections.unmodifiableList(descriptors);
181: fDescriptorMap = Collections.unmodifiableMap(descriptorMap);
182: }
183: }
184:
185: /**
186: * Sorts the column contributions.
187: *
188: * @param descriptors the descriptors to sort
189: */
190: private void sort(List descriptors) {
191: /*
192: * Topological sort of the DAG defined by the plug-in dependencies
193: * 1. TopoSort descriptors by plug-in dependency
194: * 2. Insert into Directed Acyclic Graph
195: * 3. TopoSort DAG: pick the source with the lowest gravity and remove from DAG
196: */
197: ConfigurationElementSorter sorter = new ConfigurationElementSorter() {
198: public IConfigurationElement getConfigurationElement(
199: Object object) {
200: return ((RulerColumnDescriptor) object)
201: .getConfigurationElement();
202: }
203: };
204: Object[] array = descriptors.toArray();
205: sorter.sort(array);
206:
207: Map descriptorsById = new HashMap();
208: for (int i = 0; i < array.length; i++) {
209: RulerColumnDescriptor desc = (RulerColumnDescriptor) array[i];
210: descriptorsById.put(desc.getId(), desc);
211: }
212:
213: DAG dag = new DAG();
214: for (int i = 0; i < array.length; i++) {
215: RulerColumnDescriptor desc = (RulerColumnDescriptor) array[i];
216: dag.addVertex(desc);
217:
218: Set before = desc.getPlacement().getConstraints();
219: for (Iterator it = before.iterator(); it.hasNext();) {
220: RulerColumnPlacementConstraint constraint = (RulerColumnPlacementConstraint) it
221: .next();
222: String id = constraint.getId();
223: RulerColumnDescriptor target = (RulerColumnDescriptor) descriptorsById
224: .get(id);
225: if (target == null) {
226: noteUnknownTarget(desc, id);
227: } else {
228: boolean success;
229: if (constraint.isBefore())
230: success = dag.addEdge(desc, target);
231: else
232: success = dag.addEdge(target, desc);
233: if (!success)
234: noteCycle(desc, target);
235: }
236: }
237: }
238:
239: Comparator gravityComp = new Comparator() {
240: public int compare(Object o1, Object o2) {
241: float diff = ((RulerColumnDescriptor) o1)
242: .getPlacement().getGravity()
243: - ((RulerColumnDescriptor) o2).getPlacement()
244: .getGravity();
245: if (diff == 0)
246: return 0;
247: if (diff < 0)
248: return -1;
249: return 1;
250: }
251: };
252:
253: /* Topological sort - always select the source with the least gravity */
254: Set toProcess = dag.getSources();
255: int index = 0;
256: while (!toProcess.isEmpty()) {
257: Object next = Collections.min(toProcess, gravityComp);
258: array[index] = next;
259: index++;
260: dag.removeVertex(next);
261: toProcess = dag.getSources();
262: }
263: Assert.isTrue(index == array.length);
264:
265: ListIterator it = descriptors.listIterator();
266: for (int i = 0; i < index; i++) {
267: it.next();
268: it.set(array[i]);
269: }
270: }
271:
272: private void noteInvalidExtension(IConfigurationElement element,
273: InvalidRegistryObjectException x) {
274: String message = MessageFormat.format(
275: RulerColumnMessages.RulerColumnRegistry_invalid_msg,
276: new Object[] { ExtensionPointHelper.findId(element) });
277: warnUser(message, x);
278: }
279:
280: private void noteUnknownTarget(RulerColumnDescriptor desc,
281: String referencedId) {
282: String message = MessageFormat
283: .format(
284: RulerColumnMessages.RulerColumnRegistry_unresolved_placement_msg,
285: new Object[] { QUALIFIED_EXTENSION_POINT,
286: referencedId, desc.getName(),
287: desc.getContributor() });
288: warnUser(message, null);
289: }
290:
291: private void noteCycle(RulerColumnDescriptor desc,
292: RulerColumnDescriptor target) {
293: String message = MessageFormat
294: .format(
295: RulerColumnMessages.RulerColumnRegistry_cyclic_placement_msg,
296: new Object[] { QUALIFIED_EXTENSION_POINT,
297: target.getName(), desc.getName(),
298: desc.getContributor() });
299: warnUser(message, null);
300: }
301:
302: private void noteDuplicateId(RulerColumnDescriptor desc) {
303: String message = MessageFormat
304: .format(
305: RulerColumnMessages.RulerColumnRegistry_duplicate_id_msg,
306: new Object[] { QUALIFIED_EXTENSION_POINT,
307: desc.getId(), desc.getContributor() });
308: warnUser(message, null);
309: }
310:
311: private void warnUser(String message, Exception exception) {
312: IStatus status = new Status(IStatus.WARNING,
313: TextEditorPlugin.PLUGIN_ID, IStatus.OK, message,
314: exception);
315: TextEditorPlugin.getDefault().getLog().log(status);
316: }
317: }
|