| /******************************************************************************* |
| * Copyright (c) 2004, 2007 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 |
| * Sebastian Davids - fix for the bug 178028 (removals not propagated to handlers) |
| *******************************************************************************/ |
| package org.eclipse.core.runtime.dynamichelpers; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import org.eclipse.core.internal.registry.RegistryMessages; |
| import org.eclipse.core.internal.runtime.ReferenceHashSet; |
| import org.eclipse.core.internal.runtime.RuntimeLog; |
| import org.eclipse.core.runtime.*; |
| |
| /** |
| * Implementation of the IExtensionTracker. |
| * <p> |
| * This class can be used without OSGi running. |
| * </p> |
| * @see org.eclipse.core.runtime.dynamichelpers.IExtensionTracker |
| * @since 3.1 |
| */ |
| public class ExtensionTracker implements IExtensionTracker, IRegistryChangeListener { |
| //Map keeping the association between extensions and a set of objects. Key: IExtension, value: ReferenceHashSet. |
| private Map<IExtension, ReferenceHashSet<Object>> extensionToObjects = new HashMap<>(); |
| private ListenerList<HandlerWrapper> handlers = new ListenerList<>(); |
| private final Object lock = new Object(); |
| private boolean closed = false; |
| private final IExtensionRegistry registry; // the registry that this tacker works with |
| |
| private static final Object[] EMPTY_ARRAY = new Object[0]; |
| |
| /** |
| * Construct a new instance of the extension tracker. |
| */ |
| public ExtensionTracker() { |
| this(RegistryFactory.getRegistry()); |
| } |
| |
| /** |
| * Construct a new instance of the extension tracker using the given registry |
| * containing tracked extensions and extension points. |
| * |
| * @param theRegistry the extension registry to track |
| * @since org.eclipse.equinox.registry 3.2 |
| */ |
| public ExtensionTracker(IExtensionRegistry theRegistry) { |
| registry = theRegistry; |
| if (registry != null) |
| registry.addRegistryChangeListener(this); |
| else |
| RuntimeLog.log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, RegistryMessages.registry_no_default, null)); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.runtime.dynamichelpers.IExtensionTracker#registerHandler(org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler, org.eclipse.core.runtime.dynamichelpers.IFilter) |
| */ |
| @Override |
| public void registerHandler(IExtensionChangeHandler handler, IFilter filter) { |
| synchronized (lock) { |
| if (closed) |
| return; |
| // TODO need to store the filter with the handler |
| handlers.add(new HandlerWrapper(handler, filter)); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see IExtensionTracker@unregisterHandler(IExtensionChangeHandler) |
| */ |
| @Override |
| public void unregisterHandler(IExtensionChangeHandler handler) { |
| synchronized (lock) { |
| if (closed) |
| return; |
| handlers.remove(new HandlerWrapper(handler, null)); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see IExtensionTracker@registerObject(IExtension, Object, int) |
| */ |
| @Override |
| public void registerObject(IExtension element, Object object, int referenceType) { |
| if (element == null || object == null) |
| return; |
| |
| synchronized (lock) { |
| if (closed) |
| return; |
| |
| ReferenceHashSet<Object> associatedObjects = extensionToObjects.get(element); |
| if (associatedObjects == null) { |
| associatedObjects = new ReferenceHashSet<>(); |
| extensionToObjects.put(element, associatedObjects); |
| } |
| associatedObjects.add(object, referenceType); |
| } |
| } |
| |
| /** |
| * Implementation of IRegistryChangeListener interface. |
| * <p> |
| * <em>This method must not be called by clients.</em> |
| * </p> |
| */ |
| @Override |
| public void registryChanged(IRegistryChangeEvent event) { |
| IExtensionDelta delta[] = event.getExtensionDeltas(); |
| int len = delta.length; |
| for (int i = 0; i < len; i++) |
| switch (delta[i].getKind()) { |
| case IExtensionDelta.ADDED : |
| doAdd(delta[i]); |
| break; |
| case IExtensionDelta.REMOVED : |
| doRemove(delta[i]); |
| break; |
| default : |
| break; |
| } |
| } |
| |
| /** |
| * Notify all handlers whose filter matches that the given delta occurred. |
| * If the list of objects is not <code>null</code> then this is a removal and |
| * the handlers will be given a chance to process the list. If it is <code>null</code> |
| * then the notification is an addition. |
| * |
| * @param delta the change to broadcast |
| * @param objects the objects to pass to the handlers on removals |
| */ |
| private void notify(IExtensionDelta delta, Object[] objects) { |
| // Get a copy of the handlers for safe notification |
| Object[] handlersCopy = null; |
| synchronized (lock) { |
| if (closed) |
| return; |
| |
| if (handlers == null || handlers.isEmpty()) |
| return; |
| handlersCopy = handlers.getListeners(); |
| } |
| |
| for (Object w : handlersCopy) { |
| HandlerWrapper wrapper = (HandlerWrapper) w; |
| if (wrapper.filter == null || wrapper.filter.matches(delta.getExtensionPoint())) { |
| if (objects == null) |
| applyAdd(wrapper.handler, delta.getExtension()); |
| else |
| applyRemove(wrapper.handler, delta.getExtension(), objects); |
| } |
| } |
| } |
| |
| protected void applyAdd(IExtensionChangeHandler handler, IExtension extension) { |
| handler.addExtension(this, extension); |
| } |
| |
| private void doAdd(IExtensionDelta delta) { |
| notify(delta, null); |
| } |
| |
| private void doRemove(IExtensionDelta delta) { |
| Object[] removedObjects = null; |
| synchronized (lock) { |
| if (closed) |
| return; |
| |
| ReferenceHashSet<?> associatedObjects = extensionToObjects.remove(delta.getExtension()); |
| if (associatedObjects == null) |
| removedObjects = EMPTY_ARRAY; |
| else |
| //Copy the objects early so we don't hold the lock too long |
| removedObjects = associatedObjects.toArray(); |
| } |
| notify(delta, removedObjects); |
| } |
| |
| protected void applyRemove(IExtensionChangeHandler handler, IExtension removedExtension, Object[] removedObjects) { |
| handler.removeExtension(removedExtension, removedObjects); |
| } |
| |
| /* (non-Javadoc) |
| * @see IExtensionTracker@getObjects(IExtension) |
| */ |
| @Override |
| public Object[] getObjects(IExtension element) { |
| synchronized (lock) { |
| if (closed) |
| return EMPTY_ARRAY; |
| ReferenceHashSet<?> objectSet = extensionToObjects.get(element); |
| if (objectSet == null) |
| return EMPTY_ARRAY; |
| |
| return objectSet.toArray(); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see IExtensionTracker@close() |
| */ |
| @Override |
| public void close() { |
| synchronized (lock) { |
| if (closed) |
| return; |
| if (registry != null) |
| registry.removeRegistryChangeListener(this); |
| extensionToObjects = null; |
| handlers = null; |
| |
| closed = true; |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see IExtensionTracker@unregisterObject(IExtension, Object) |
| */ |
| @Override |
| public void unregisterObject(IExtension extension, Object object) { |
| synchronized (lock) { |
| if (closed) |
| return; |
| ReferenceHashSet<Object> associatedObjects = extensionToObjects.get(extension); |
| if (associatedObjects != null) |
| associatedObjects.remove(object); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see IExtensionTracker@unregisterObject(IExtension) |
| */ |
| @Override |
| public Object[] unregisterObject(IExtension extension) { |
| synchronized (lock) { |
| if (closed) |
| return EMPTY_ARRAY; |
| ReferenceHashSet<?> associatedObjects = extensionToObjects.remove(extension); |
| if (associatedObjects == null) |
| return EMPTY_ARRAY; |
| return associatedObjects.toArray(); |
| } |
| } |
| |
| /** |
| * Return an instance of filter matching all changes for the given extension point. |
| * |
| * @param xpt the extension point |
| * @return a filter |
| */ |
| public static IFilter createExtensionPointFilter(final IExtensionPoint xpt) { |
| return xpt::equals; |
| } |
| |
| /** |
| * Return an instance of filter matching all changes for the given extension points. |
| * |
| * @param xpts the extension points used to filter |
| * @return a filter |
| */ |
| public static IFilter createExtensionPointFilter(final IExtensionPoint[] xpts) { |
| return (IExtensionPoint target) -> { |
| for (IExtensionPoint xpt : xpts) { |
| if (xpt.equals(target)) { |
| return true; |
| } |
| } |
| return false; |
| }; |
| } |
| |
| /** |
| * Return an instance of filter matching all changes from a given plugin. |
| * |
| * @param id the plugin id |
| * @return a filter |
| */ |
| public static IFilter createNamespaceFilter(final String id) { |
| return (IExtensionPoint target) -> id.equals(target.getNamespaceIdentifier()); |
| } |
| |
| private class HandlerWrapper { |
| IExtensionChangeHandler handler; |
| IFilter filter; |
| |
| public HandlerWrapper(IExtensionChangeHandler handler, IFilter filter) { |
| this.handler = handler; |
| this.filter = filter; |
| } |
| |
| @Override |
| public boolean equals(Object target) { |
| return handler.equals(((HandlerWrapper) target).handler); |
| } |
| |
| @Override |
| public int hashCode() { |
| return handler.hashCode(); |
| } |
| } |
| |
| } |