Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Make the registration of IResourceChangeListener as services more
visible and convenient.

#52
  • Loading branch information
laeubi committed Apr 16, 2022
1 parent 8eba62e commit c30b669
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 38 deletions.
16 changes: 16 additions & 0 deletions bundles/org.eclipse.core.resources/.settings/.api_filters
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.core.resources" version="2">
<resource path="src/org/eclipse/core/resources/IResourceChangeListener.java" type="org.eclipse.core.resources.IResourceChangeListener">
<filter id="403767336">
<message_arguments>
<message_argument value="org.eclipse.core.resources.IResourceChangeListener"/>
<message_argument value="PROPERTY_EVENT_MASK"/>
</message_arguments>
</filter>
<filter id="1211105284">
<message_arguments>
<message_argument value="getMaskProperties(int)"/>
</message_arguments>
</filter>
</resource>
</component>
Expand Up @@ -14,7 +14,8 @@
package org.eclipse.core.internal.resources;

import java.util.Map;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.osgi.service.log.Logger;
import org.osgi.service.log.LoggerFactory;
Expand All @@ -28,39 +29,9 @@
* instances as services, and be called back when changes occur, in the same way
* that (for example) {@link DebugOptionsListener} is used to receive callbacks.
* </p>
* <p>
* The services can also be registered with Declarative Services, which allows a
* bundle to not require that the Workspace bundle be started prior to accessing
* the resources, as until the IWorkspace is available the bundle will not need
* any callbacks. This will also save potential NPEs when the {@link IWorkspace}
* shuts down, because the OSGi runtime will handle the deregistration of
* services automatically.
* </p>
* <p>
* Services registered with an <code>event.mask</code> property can be used to
* receive a sub-set of the events, by registering the value with the
* {@link IWorkspace#addResourceChangeListener(IResourceChangeListener, int)}
* method. This allows (for example) {@link IResourceChangeEvent#POST_CHANGE}
* events to be received by setting <code>event.mask=1</code> in the service
* registration.
* </p>
* <p>
* The following can be used to register a listener with Declarative Services:
* </p>
*
* <pre>
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.4.0" immediate="true" name="ExampleResourceListener"&gt;
&lt;implementation class="org.example.ExampleResourceListener"/&gt;
&lt;service&gt;
&lt;provide interface="org.eclipse.core.resources.IResourceChangeListener"/&gt;
&lt;/service&gt;
&lt;!-- 1 == IResourceChangeEvent.POST_CHANGE -->
&lt;property name="event.mask" type="Integer" value="1"/&gt;
&lt;/scr:component&gt;
* </pre>
*/
public final class ResourceChangeListenerRegistrar {

private final IWorkspace workspace;
private Logger logger;

Expand All @@ -83,7 +54,7 @@ public ResourceChangeListenerRegistrar(IWorkspace workspace) {
*/
public void addResourceChangeListener(IResourceChangeListener listener, Map<String, Object> properties) {
// TODO Add as public API https://bugs.eclipse.org/bugs/show_bug.cgi?id=564985
Object mask = properties.get("event.mask"); //$NON-NLS-1$
Object mask = properties.get(IResourceChangeListener.PROPERTY_EVENT_MASK);
if (mask instanceof Integer) {
workspace.addResourceChangeListener(listener, ((Integer) mask).intValue());
} else {
Expand Down
Expand Up @@ -14,22 +14,75 @@
*******************************************************************************/
package org.eclipse.core.resources;

import java.util.EventListener;
import java.util.*;

/**
* A resource change listener is notified of changes to resources
* in the workspace.
* These changes arise from direct manipulation of resources, or
* A resource change listener is notified of changes to resources in the
* workspace. These changes arise from direct manipulation of resources, or
* indirectly through re-synchronization with the local file system.
* <p>
* Clients may implement this interface.
* </p>
*
* There are two ways to register a listener:
* <ol>
* <li>One could direct registration with
* IWorkspace#addResourceChangeListener(IResourceChangeListener, int) users
* should note that they are responsible to remove the listener if no longer
* needed to prevent memory leaks.</li>
* <li>One could register an OSGi Service making and it will automatically be
* picked up leveraging the <a href=
* "https://enroute.osgi.org/FAQ/400-patterns.html#whiteboard-pattern">Whiteboard
* Pattern</a>. Services registered with an {@link #PROPERTY_EVENT_MASK}
* property can be used to receive a sub-set of the events, by registering the
* value with the
* {@link IWorkspace#addResourceChangeListener(IResourceChangeListener, int)}
* method. This allows (for example) {@link IResourceChangeEvent#POST_CHANGE}
* events to be received by setting <code>event.mask=1</code> in the service
* registration.</li>
* </ol>
* <p>
* For example the services can be registered with Declarative Services, which
* allows a bundle to not require that the Workspace bundle be started prior to
* accessing the resources, as until the IWorkspace is available the bundle will
* not need any callbacks. This will also save potential NPEs when the
* {@link IWorkspace} shuts down, because the OSGi runtime will handle the
* deregistration of services automatically:
*
* <pre>
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.4.0" immediate="true" name="ExampleResourceListener"&gt;
&lt;implementation class="org.example.ExampleResourceListener"/&gt;
&lt;service&gt;
&lt;provide interface="org.eclipse.core.resources.IResourceChangeListener"/&gt;
&lt;/service&gt;
&lt;!-- 1 == IResourceChangeEvent.POST_CHANGE -->
&lt;property name="event.mask" type="Integer" value="1"/&gt;
&lt;/scr:component&gt;
* </pre>
* </p>
* <p>
* If you choose to register it with the core OSGi API (e.g. in an activator)
* you can use the following pattern:
*
* <pre>
* bundleContext.registerService(IResourceChangeListener.class, myListener, IResourceChangeListener.getMaskProperties(
* IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE));
* </pre>
* </p>
*
*
* @see IResourceDelta
* @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int)
*/

@FunctionalInterface
public interface IResourceChangeListener extends EventListener {

/**
* @since 3.17
*/
String PROPERTY_EVENT_MASK = "event.mask"; //$NON-NLS-1$

/**
* Notifies this listener that some resource changes
* are happening, or have already happened.
Expand All @@ -50,4 +103,20 @@ public interface IResourceChangeListener extends EventListener {
* @see IResourceDelta
*/
void resourceChanged(IResourceChangeEvent event);

/**
* Creates a {@link Dictionary} suitable to be used when registering a
* {@link IResourceChangeListener} as an OSGi service.
*
* @param mask see
* {@link IWorkspace#addResourceChangeListener(IResourceChangeListener, int)}
* @return a new {@link Dictionary} representing the OSGi service properties for
* the given mask
* @since 3.17
*/
static Dictionary<String, ?> getMaskProperties(int mask) {
Dictionary<String, Object> properties = new Hashtable<>();
properties.put(PROPERTY_EVENT_MASK, mask);
return properties;
}
}

0 comments on commit c30b669

Please sign in to comment.