blob: 644cc1b4bc1d4aaadc2802653188f0668412c8e8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.registry;
import java.lang.reflect.Array;
import java.util.*;
import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.core.internal.runtime.Policy;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
/**
* An OSGi-free implementation for the extension registry API.
*/
public class ExtensionRegistry extends RegistryModelObject implements IExtensionRegistry {
private static final String OPTION_DEBUG_EVENTS_EXTENSION = "org.eclipse.core.runtime/registry/debug/events/extension"; //$NON-NLS-1$
public static boolean DEBUG;
// an id->element mapping
private Map elements = new HashMap(11);
// all registry change listeners
private transient ListenerList listeners = new ListenerList(10);
// deltas not broadcasted yet
private transient Map deltas = new HashMap(11);
// extensions without extension point
private Map orphanExtensions = new HashMap(11);
private Map orphanFragments = new HashMap(11);
private transient IExtensionLinker linker;
private transient RegistryCacheReader reader = null;
private transient boolean isDirty = false;
class ListenerInfo {
IRegistryChangeListener listener;
String filter;
public ListenerInfo(IRegistryChangeListener listener, String filter) {
this.listener = listener;
this.filter = filter;
}
/**
* Used by ListenerList to ensure uniqueness.
*/
public boolean equals(Object another) {
return another instanceof ListenerInfo && ((ListenerInfo) another).listener == this.listener;
}
}
public void add(IRegistryElement[] elements) {
synchronized (this) {
isDirty = true;
for (int i = 0; i < elements.length; i++)
basicAdd(elements[i], true);
fireRegistryChangeEvent();
}
}
/**
* Adds and resolves all extensions and extension points provided by the
* plug-in.
* <p>
* A corresponding IRegistryChangeEvent will be broadcast to all listeners
* interested on changes in the given plug-in.
* </p>
*/
public void add(IRegistryElement element) {
synchronized (this) {
isDirty = true;
basicAdd(element, true);
fireRegistryChangeEvent();
}
}
void basicAdd(IRegistryElement element, boolean link) {
//TODO: this should be logged
if (elements.containsKey(element.getUniqueIdentifier())) {
if (DEBUG)
System.out.println("********* Element already added: " + element.getUniqueIdentifier() + " - ignored."); //$NON-NLS-1$//$NON-NLS-2$
return;
}
elements.put(element.getUniqueIdentifier(), element);
((BundleModel) element).setParent(this);
if (!link)
return;
if (element.isFragment()) {
addFragmentTo(element.getUniqueIdentifier(), element.getHostIdentifier());
// if the master is not present yet, don't add anything
if (!elements.containsKey(element.getHostIdentifier()))
return;
} else {
Collection fragmentNames = getFragmentNames(element.getUniqueIdentifier());
for (Iterator iter = fragmentNames.iterator(); iter.hasNext();) {
IRegistryElement fragment = (IRegistryElement) elements.get(iter.next());
addExtensionsAndExtensionPoints(fragment);
}
}
addExtensionsAndExtensionPoints(element);
}
private void addExtensionsAndExtensionPoints(IRegistryElement element) {
// now add and resolve extensions and extension points
IExtensionPoint[] extPoints = element.getExtensionPoints();
for (int i = 0; i < extPoints.length; i++)
this.addExtensionPoint(extPoints[i]);
IExtension[] extensions = element.getExtensions();
for (int i = 0; i < extensions.length; i++)
this.addExtension(extensions[i]);
}
/* Utility method to help with array concatenations */
private Object addArrays(Object a, Object b) {
Object[] result = (Object[]) Array.newInstance(a.getClass().getComponentType(), Array.getLength(a) + Array.getLength(b));
System.arraycopy(a, 0, result, 0, Array.getLength(a));
System.arraycopy(b, 0, result, Array.getLength(a), Array.getLength(b));
return result;
}
/*
* Creates an association between a fragment and a master element.
*/
private void addFragmentTo(String fragmentName, String masterName) {
Set fragmentNames = (Set) this.orphanFragments.get(masterName);
if (fragmentNames == null)
orphanFragments.put(masterName, fragmentNames = new HashSet());
fragmentNames.add(fragmentName);
}
/*
* Removes an association between a fragment and a master element.
*/
private void removeFragmentFrom(String fragmentName, String masterName) {
Set fragmentNames = (Set) this.orphanFragments.get(masterName);
if (fragmentNames == null)
return;
fragmentNames.remove(fragmentName);
if (fragmentNames.isEmpty())
orphanFragments.remove(masterName);
}
/*
* Returns a collection of fragments for a master element.
*/
private Collection getFragmentNames(String masterName) {
Collection fragmentNames = (Collection) orphanFragments.get(masterName);
return fragmentNames == null ? Collections.EMPTY_SET : fragmentNames;
}
private void addExtension(IExtension extension) {
IExtensionPoint extPoint = getExtensionPoint(extension.getExtensionPointUniqueIdentifier());
//orphan extension
if (extPoint == null) {
// are there any other orphan extensions
IExtension[] existingOrphanExtensions = (IExtension[]) orphanExtensions.get(extension.getExtensionPointUniqueIdentifier());
if (existingOrphanExtensions != null) {
// just add
IExtension[] newOrphanExtensions = new IExtension[existingOrphanExtensions.length + 1];
System.arraycopy(existingOrphanExtensions, 0, newOrphanExtensions, 0, existingOrphanExtensions.length);
newOrphanExtensions[newOrphanExtensions.length - 1] = extension;
orphanExtensions.put(extension.getExtensionPointUniqueIdentifier(), newOrphanExtensions);
} else
// otherwise this is the first one
orphanExtensions.put(extension.getExtensionPointUniqueIdentifier(), new IExtension[] {extension});
return;
}
// otherwise, link them
IExtension[] newExtensions;
IExtension[] existingExtensions = extPoint.getExtensions();
if (existingExtensions == null)
newExtensions = new IExtension[] {extension};
else {
newExtensions = new IExtension[existingExtensions.length + 1];
System.arraycopy(existingExtensions, 0, newExtensions, 0, existingExtensions.length);
newExtensions[newExtensions.length - 1] = extension;
}
linker.link(extPoint, newExtensions);
recordChange(extPoint, extension, IExtensionDelta.ADDED);
}
/**
* Looks for existing orphan extensions to connect to the given extension
* point. If none is found, there is nothing to do. Otherwise, link them.
*/
private void addExtensionPoint(IExtensionPoint extPoint) {
IExtension[] existingExtensions = (IExtension[]) orphanExtensions.remove(extPoint.getUniqueIdentifier());
if (existingExtensions == null)
return;
// otherwise, link them
linker.link(extPoint, existingExtensions);
recordChange(extPoint, existingExtensions, IExtensionDelta.ADDED);
}
/**
* Adds the given listener for registry change events on the given plug-in.
*/
// TODO This should be thread safe code. May get called by multiple threads.
public void addRegistryChangeListener(IRegistryChangeListener listener, String filter) {
this.listeners.add(new ListenerInfo(listener, filter));
}
/**
* Adds the given listener for registry change events.
*/
public void addRegistryChangeListener(IRegistryChangeListener listener) {
addRegistryChangeListener(listener, null);
}
/**
* Broadcasts (asynchronously) the event to all interested parties.
*/
private void fireRegistryChangeEvent() {
// if there is nothing to say, just bail out
if (deltas.isEmpty() || listeners.isEmpty())
return;
// for thread safety, create tmp collections
Object[] tmpListeners = listeners.getListeners();
Map tmpDeltas = new HashMap(this.deltas);
// the deltas have been saved for notification - we can clear them now
deltas.clear();
// do the notification asynchronously
new ExtensionEventDispatcherJob(tmpListeners, tmpDeltas).schedule();
}
private RegistryDelta getDelta(String elementName) {
// is there a delta for the plug-in?
RegistryDelta existingDelta = (RegistryDelta) deltas.get(elementName);
if (existingDelta != null)
return existingDelta;
//if not, create one
RegistryDelta delta = new RegistryDelta(elementName);
deltas.put(elementName, delta);
return delta;
}
public String[] getElementIdentifiers() {
return getNamespaces();
}
public String[] getNamespaces() {
return (String[]) elements.keySet().toArray(new String[elements.size()]);
}
public IConfigurationElement[] getConfigurationElementsFor(String extensionPointId) {
int lastdot = extensionPointId.lastIndexOf('.');
if (lastdot == -1)
return new IConfigurationElement[0];
return getConfigurationElementsFor(extensionPointId.substring(0, lastdot), extensionPointId.substring(lastdot + 1));
}
public IConfigurationElement[] getConfigurationElementsFor(String pluginId, String extensionPointSimpleId) {
IExtensionPoint extPoint = this.getExtensionPoint(pluginId, extensionPointSimpleId);
if (extPoint == null)
return new IConfigurationElement[0];
IExtension[] extensions = extPoint.getExtensions();
if (extensions.length == 0)
return new IConfigurationElement[0];
Collection result = new ArrayList();
for (int i = 0; i < extensions.length; i++) {
IConfigurationElement[] toAdd = extensions[i].getConfigurationElements();
for (int j = 0; j < toAdd.length; j++)
result.add(toAdd[j]);
}
return (IConfigurationElement[]) result.toArray(new IConfigurationElement[result.size()]);
}
public IConfigurationElement[] getConfigurationElementsFor(String pluginId, String extensionPointName, String extensionId) {
IExtension extension = this.getExtension(pluginId, extensionPointName, extensionId);
if (extension == null)
return new IConfigurationElement[0];
return extension.getConfigurationElements();
}
public IExtension[] getExtensions(String elementName) {
IRegistryElement element = (IRegistryElement) elements.get(elementName);
if (element == null)
return new IExtension[0];
Collection fragmentNames = getFragmentNames(elementName);
IExtension[] allExtensions = element.getExtensions();
for (Iterator iter = fragmentNames.iterator(); iter.hasNext();) {
IRegistryElement fragment = (IRegistryElement) elements.get(iter.next());
allExtensions = (IExtension[]) addArrays(allExtensions, fragment.getExtensions());
}
return allExtensions;
}
public IExtension getExtension(String extensionPointId, String extensionId) {
int lastdot = extensionPointId.lastIndexOf('.');
if (lastdot == -1)
return null;
return getExtension(extensionPointId.substring(0, lastdot), extensionPointId.substring(lastdot + 1), extensionId);
}
public IExtension getExtension(String pluginId, String extensionPointName, String extensionId) {
IExtensionPoint extPoint = getExtensionPoint(pluginId, extensionPointName);
if (extPoint != null)
return extPoint.getExtension(extensionId);
return null;
}
public IExtensionPoint[] getExtensionPoints(String elementName) {
IRegistryElement element = (IRegistryElement) elements.get(elementName);
if (element == null)
return new IExtensionPoint[0];
Collection fragmentNames = getFragmentNames(elementName);
IExtensionPoint[] allExtensionPoints = element.getExtensionPoints();
for (Iterator iter = fragmentNames.iterator(); iter.hasNext();) {
IRegistryElement fragment = (IRegistryElement) elements.get(iter.next());
allExtensionPoints = (IExtensionPoint[]) addArrays(allExtensionPoints, fragment.getExtensionPoints());
}
return allExtensionPoints;
}
public IExtensionPoint[] getExtensionPoints() {
ArrayList extensionPoints = new ArrayList();
for (Iterator iter = elements.values().iterator(); iter.hasNext();) {
IRegistryElement model = (IRegistryElement) iter.next();
IExtensionPoint[] toAdd = model.getExtensionPoints();
for (int i = 0; i < toAdd.length; i++)
extensionPoints.add(toAdd[i]);
}
return (IExtensionPoint[]) extensionPoints.toArray(new IExtensionPoint[extensionPoints.size()]);
}
public IExtensionPoint getExtensionPoint(String xptUniqueId) {
int lastdot = xptUniqueId.lastIndexOf('.');
if (lastdot == -1)
return null;
return getExtensionPoint(xptUniqueId.substring(0, lastdot), xptUniqueId.substring(lastdot + 1));
}
public IExtensionPoint getExtensionPoint(String elementName, String xpt) {
IRegistryElement element = (IRegistryElement) elements.get(elementName);
if (element == null)
return null;
IExtensionPoint extPoint = element.getExtensionPoint(xpt);
if (extPoint != null)
return extPoint;
Collection fragmentNames = getFragmentNames(elementName);
for (Iterator iter = fragmentNames.iterator(); iter.hasNext();) {
extPoint = ((IRegistryElement) elements.get(iter.next())).getExtensionPoint(xpt);
if (extPoint != null)
return extPoint;
}
return null;
}
/*
* Records an extension addition/removal.
*/
private void recordChange(IExtensionPoint extPoint, IExtension extension, int kind) {
// avoid computing deltas when there are no listeners
if (listeners.isEmpty())
return;
ExtensionDelta extensionDelta = new ExtensionDelta();
extensionDelta.setExtension(extension);
extensionDelta.setExtensionPoint(extPoint);
extensionDelta.setKind(kind);
getDelta(extPoint.getNamespace()).addExtensionDelta(extensionDelta);
}
/*
* Records a set of extension additions/removals.
*/
private void recordChange(IExtensionPoint extPoint, IExtension[] extensions, int kind) {
if (listeners.isEmpty())
return;
if (extensions.length == 0)
return;
RegistryDelta pluginDelta = getDelta(extPoint.getNamespace());
for (int i = 0; i < extensions.length; i++) {
ExtensionDelta extensionDelta = new ExtensionDelta();
extensionDelta.setExtension(extensions[i]);
extensionDelta.setExtensionPoint(extPoint);
extensionDelta.setKind(kind);
pluginDelta.addExtensionDelta(extensionDelta);
}
}
/**
* Unresolves and removes all extensions and extension points provided by
* the plug-in.
* <p>
* A corresponding IRegistryChangeEvent will be broadcast to all listeners
* interested on changes in the given plug-in.
* </p>
*/
public boolean remove(String elementName, long bundleId) {
synchronized (this) {
IRegistryElement element = (IRegistryElement) elements.get(elementName);
if (element == null) {
if (DEBUG)
System.out.println("********* Element unknown: " + elementName + " - not removed."); //$NON-NLS-1$//$NON-NLS-2$
return false;
}
if (element.getId() != bundleId)
return false;
isDirty = true;
if (element.isFragment()) {
// if the master is not present yet, bail out
if (!elements.containsKey(element.getHostIdentifier())) {
removeFragmentFrom(elementName, element.getHostIdentifier());
elements.remove(elementName);
return true;
}
} else {
Collection fragmentNames = getFragmentNames(element.getUniqueIdentifier());
for (Iterator iter = fragmentNames.iterator(); iter.hasNext();) {
IRegistryElement fragment = (IRegistryElement) elements.get(iter.next());
removeExtensionsAndExtensionPoints(fragment);
}
}
removeExtensionsAndExtensionPoints(element);
// remove link between master and fragment
removeFragmentFrom(elementName, element.getHostIdentifier());
// remove it in the end
elements.remove(elementName);
fireRegistryChangeEvent();
}
return true;
}
private void removeExtensionsAndExtensionPoints(IRegistryElement element) {
// remove extensions
IExtension[] extensions = element.getExtensions();
for (int i = 0; i < extensions.length; i++)
this.removeExtension(extensions[i]);
// remove extension points
IExtensionPoint[] extPoints = element.getExtensionPoints();
for (int i = 0; i < extPoints.length; i++)
this.removeExtensionPoint(extPoints[i]);
}
private void removeExtensionPoint(IExtensionPoint extPoint) {
if (extPoint.getExtensions() != null) {
IExtension[] existingExtensions = extPoint.getExtensions();
orphanExtensions.put(extPoint.getUniqueIdentifier(), existingExtensions);
linker.link(extPoint, null);
recordChange(extPoint, existingExtensions, IExtensionDelta.REMOVED);
}
}
private void removeExtension(IExtension extension) {
IExtensionPoint extPoint = getExtensionPoint(extension.getExtensionPointUniqueIdentifier());
if (extPoint == null) {
// not found - maybe it was an orphan extension
IExtension[] existingOrphanExtensions = (IExtension[]) orphanExtensions.get(extension.getExtensionPointUniqueIdentifier());
if (existingOrphanExtensions == null)
// nope, this extension is unknown
return;
// yes, so just remove it from the orphans list
IExtension[] newOrphanExtensions = new IExtension[existingOrphanExtensions.length - 1];
for (int i = 0, j = 0; i < existingOrphanExtensions.length; i++)
if (extension != existingOrphanExtensions[i])
newOrphanExtensions[j++] = existingOrphanExtensions[i];
orphanExtensions.put(extension.getExtensionPointUniqueIdentifier(), newOrphanExtensions);
return;
}
// otherwise, unlink the extension from the extension point
IExtension[] existingExtensions = extPoint.getExtensions();
IExtension[] newExtensions = null;
if (existingExtensions.length >= 1) {
newExtensions = new IExtension[existingExtensions.length - 1];
for (int i = 0, j = 0; i < existingExtensions.length; i++)
if (existingExtensions[i] != extension)
newExtensions[j++] = existingExtensions[i];
}
linker.link(extPoint, newExtensions);
recordChange(extPoint, extension, IExtensionDelta.REMOVED);
}
/**
* Adds the given listener for registry change events related to this
* plug-in's extension points.
*/
// TODO This should be thread safe code. May get called by multiple threads.
public void removeRegistryChangeListener(IRegistryChangeListener listener) {
this.listeners.remove(new ListenerInfo(listener, null));
}
public ExtensionRegistry(IExtensionLinker extensionLinker) {
linker = extensionLinker;
String debugOption = InternalPlatform.getDefault().getOption(OPTION_DEBUG_EVENTS_EXTENSION);
DEBUG = debugOption == null ? false : debugOption.equalsIgnoreCase("true"); //$NON-NLS-1$
if (DEBUG)
addRegistryChangeListener(new IRegistryChangeListener() {
public void registryChanged(IRegistryChangeEvent event) {
System.out.println(event);
}
});
}
private final static class ExtensionEventDispatcherJob extends Job {
// an "identy rule" that forces extension events to be queued
private final static ISchedulingRule EXTENSION_EVENT_RULE = new ISchedulingRule() {
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
};
private Object[] listenerInfos;
private Map deltas;
public ExtensionEventDispatcherJob(Object[] listenerInfos, Map deltas) {
super("RegistryChangeEventDispatcherJob"); //$NON-NLS-1$
this.listenerInfos = listenerInfos;
this.deltas = deltas;
// all extension event dispatching jobs use this rule
setRule(EXTENSION_EVENT_RULE);
}
public IStatus run(IProgressMonitor monitor) {
MultiStatus result = new MultiStatus(IPlatform.PI_RUNTIME, IStatus.OK, Policy.bind("plugin.eventListenerError"), null); //$NON-NLS-1$
for (int i = 0; i < listenerInfos.length; i++) {
ListenerInfo listenerInfo = (ListenerInfo) listenerInfos[i];
if (listenerInfo.filter != null && !deltas.containsKey(listenerInfo.filter))
continue;
try {
listenerInfo.listener.registryChanged(new RegistryChangeEvent(deltas, listenerInfo.filter));
} catch (RuntimeException re) {
String message = re.getMessage() == null ? "" : re.getMessage(); //$NON-NLS-1$
result.add(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IStatus.OK, message, re));
}
}
return result;
}
}
public IRegistryElement getElement(String elementId) {
return (IRegistryElement) elements.get(elementId);
}
public void setCacheReader(RegistryCacheReader value) {
reader = value;
}
public RegistryCacheReader getCacheReader() {
return reader;
}
public boolean isDirty() {
return isDirty;
}
public void setDirty(boolean value) {
isDirty = value;
}
}