Author: cziegeler
Date: Tue Oct 18 07:36:36 2016
New Revision: 1765397

URL: http://svn.apache.org/viewvc?rev=1765397&view=rev
Log:
SLING-6056 : achieve 1:1 mapping between observation and resource change 
listener

Modified:
    
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java
    
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java
    
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java

Modified: 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java?rev=1765397&r1=1765396&r2=1765397&view=diff
==============================================================================
--- 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java
 (original)
+++ 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java
 Tue Oct 18 07:36:36 2016
@@ -21,18 +21,18 @@ package org.apache.sling.resourceresolve
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
 import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.api.resource.path.PathSet;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
 import org.apache.sling.spi.resource.provider.ObserverConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Implementation of the observation reporter.
@@ -40,15 +40,18 @@ import org.apache.sling.spi.resource.pro
  */
 public class BasicObservationReporter implements ObservationReporter {
 
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
+    /** List of observer configurations for the provider. */
     private final List<ObserverConfiguration> configs;
 
-    private final Map<ListenerConfig, List<ResourceChangeListenerInfo>> 
listeners = new HashMap<BasicObservationReporter.ListenerConfig, 
List<ResourceChangeListenerInfo>>();;
-
+    /** The search path. */
     private final String[] searchPath;
 
     /**
      * Create a reporter listening for resource provider changes
+     *
+     * @param searchPath The search path
      * @param infos The listeners map
      */
     public BasicObservationReporter(
@@ -56,20 +59,26 @@ public class BasicObservationReporter im
             final Collection<ResourceChangeListenerInfo> infos) {
         this.searchPath = searchPath;
         final Set<String> paths = new HashSet<String>();
+        final List<ResourceChangeListenerInfo> result = new ArrayList<>();
         for(final ResourceChangeListenerInfo info : infos) {
             if ( !info.getProviderChangeTypes().isEmpty() ) {
                 for(final Path p : info.getPaths()) {
                     paths.add(p.getPath());
                 }
-                fillListeners(info, info.getResourceChangeTypes());
+                result.add(info);
             }
         }
-        final ObserverConfiguration cfg = new 
BasicObserverConfiguration(PathSet.fromStringCollection(paths));
-        this.configs = Collections.singletonList(cfg);
+        final BasicObserverConfiguration cfg = new 
BasicObserverConfiguration(PathSet.fromStringCollection(paths));
+        for(final ResourceChangeListenerInfo i : infos) {
+            cfg.addListener(i);
+        }
+        this.configs = Collections.singletonList((ObserverConfiguration)cfg);
     }
 
     /**
      * Create a reporter listening for a provider
+     *
+     * @param searchPath The search paths
      * @param infos The listeners map
      * @param providerPath The mount point of the provider
      * @param excludePaths Excluded paths for that provider
@@ -77,49 +86,75 @@ public class BasicObservationReporter im
     public BasicObservationReporter(
             final String[] searchPath,
             final Collection<ResourceChangeListenerInfo> infos,
-            final Path providerPath, final PathSet excludePaths) {
+            final Path providerPath,
+            final PathSet excludePaths) {
         this.searchPath = searchPath;
 
-        final Map<String, ObserverConfig> configMap = new HashMap<String, 
ObserverConfig>();
+        final List<ObserverConfiguration> observerConfigs = new ArrayList<>();
         for(final ResourceChangeListenerInfo info : infos) {
             if ( !info.getResourceChangeTypes().isEmpty() ) {
-                boolean add = false;
+                // find the set of paths that match the provider
+                final Set<Path> paths = new HashSet<>();
                 for(final Path p : info.getPaths()) {
                     if ( providerPath.matches(p.getPath()) && 
excludePaths.matches(p.getPath()) == null ) {
-                        ObserverConfig config = configMap.get(p);
-                        if ( config == null ) {
-                            config = new ObserverConfig();
-                            configMap.put(p.getPath(), config);
+                        paths.add(p);
+                    }
+                }
+                if ( !paths.isEmpty() ) {
+                    final PathSet pathSet = PathSet.fromPathCollection(paths);
+                    // search for an existing configuration with the same 
paths and hints
+                    BasicObserverConfiguration found = null;
+                    for(final ObserverConfiguration c : observerConfigs) {
+                        if ( c.getPaths().equals(pathSet)
+                            && (( c.getPropertyNamesHint() == null && 
info.getPropertyNamesHint() == null)
+                               || c.getPropertyNamesHint() != null && 
c.getPropertyNamesHint().equals(info.getPropertyNamesHint()))) {
+                            found = (BasicObserverConfiguration)c;
+                            break;
                         }
-                        config.types.addAll(info.getResourceChangeTypes());
-                        if ( info.isExternal() ) {
-                            config.isExternal = true;
+                    }
+                    final BasicObserverConfiguration config;
+                    if ( found != null ) {
+                        // check external and types
+                        boolean createNew = false;
+                        if ( !found.includeExternal() && info.isExternal() ) {
+                            createNew = true;
+                        }
+                        if ( 
!found.getChangeTypes().equals(info.getResourceChangeTypes()) ) {
+                            createNew = true;
                         }
-                        add = true;
+                        if ( createNew ) {
+                            // create new/updated config
+                            observerConfigs.remove(found);
+                            final Set<ResourceChange.ChangeType> types = new 
HashSet<>();
+                            types.addAll(found.getChangeTypes());
+                            types.addAll(info.getResourceChangeTypes());
+                            config = new BasicObserverConfiguration(pathSet,
+                                types,
+                                info.isExternal() && found.includeExternal(),
+                                found.getExcludedPaths(),
+                                found.getPropertyNamesHint());
+                            observerConfigs.add(config);
+                            for(final ResourceChangeListenerInfo i : 
found.getListeners()) {
+                                config.addListener(i);
+                            }
+
+                        } else {
+                            config = found;
+                        }
+                    } else {
+                        // create new config
+                        config = new BasicObserverConfiguration(pathSet,
+                            info.getResourceChangeTypes(),
+                            info.isExternal(),
+                            excludePaths.getSubset(pathSet),
+                            info.getPropertyNamesHint());
+                        observerConfigs.add(config);
                     }
-                }
-                if ( add ) {
-                    fillListeners(info, info.getResourceChangeTypes());
+                    config.addListener(info);
                 }
             }
         }
-        final List<ObserverConfiguration> result = new 
ArrayList<ObserverConfiguration>();
-        for(final Map.Entry<String, ObserverConfig> entry : 
configMap.entrySet()) {
-            final ObserverConfiguration cfg = new 
BasicObserverConfiguration(entry.getKey(), entry.getValue().types,
-                    entry.getValue().isExternal, excludePaths);
-            result.add(cfg);
-        }
-        this.configs = Collections.unmodifiableList(result);
-    }
-
-    private void fillListeners(final ResourceChangeListenerInfo info, final 
Set<ChangeType> types) {
-        final ListenerConfig cfg = new ListenerConfig(info, types);
-        List<ResourceChangeListenerInfo> list = this.listeners.get(cfg);
-        if ( list == null ) {
-            list = new ArrayList<ResourceChangeListenerInfo>();
-            this.listeners.put(cfg, list);
-        }
-        list.add(info);
+        this.configs = Collections.unmodifiableList(observerConfigs);
     }
 
     @Override
@@ -129,21 +164,56 @@ public class BasicObservationReporter im
 
     @Override
     public void reportChanges(final Iterable<ResourceChange> changes, final 
boolean distribute) {
-        for (final Map.Entry<ListenerConfig, List<ResourceChangeListenerInfo>> 
entry : this.listeners.entrySet()) {
-            final List<ResourceChange> filtered = filterChanges(changes, 
entry.getKey());
-            if ( !filtered.isEmpty() ) {
-                for(final ResourceChangeListenerInfo info : entry.getValue()) {
-                    info.getListener().onChange(filtered);
-                }
+        for(final ObserverConfiguration cfg : this.configs) {
+            final List<ResourceChange> filteredChanges = 
filterChanges(changes, cfg);
+            if (!filteredChanges.isEmpty() ) {
+                this.reportChanges(cfg, filteredChanges, distribute);
             }
         }
-        // TODO implement distribute
     }
 
     @Override
-    public void reportChanges(ObserverConfiguration config, 
Iterable<ResourceChange> changes, boolean distribute) {
-        // TODO Auto-generated method stub
-        this.reportChanges(changes, distribute);
+    public void reportChanges(final ObserverConfiguration config,
+            final Iterable<ResourceChange> changes,
+            final boolean distribute) {
+        if ( config != null && config instanceof BasicObserverConfiguration ) {
+            final BasicObserverConfiguration observerConfig = 
(BasicObserverConfiguration)config;
+
+            ResourceChangeListenerInfo previousInfo = null;
+            List<ResourceChange> filteredChanges = null;
+            for(final ResourceChangeListenerInfo info : 
observerConfig.getListeners()) {
+                if ( previousInfo == null || !equals(previousInfo, info) ) {
+                    filteredChanges = filterChanges(changes, info);
+                    previousInfo = info;
+                }
+                if ( !filteredChanges.isEmpty() ) {
+                    final ResourceChangeListener listener = info.getListener();
+                    if ( listener != null ) {
+                        listener.onChange(filteredChanges);
+                    }
+                }
+            }
+            // TODO implement distribute
+            if ( distribute ) {
+                logger.error("Distrubte flag is send for observation events, 
however distribute is currently not implemented!");
+            }
+        }
+    }
+
+    /**
+     * Test if two resource change listener infos are equal wrt external and 
change types
+     * @param infoA First info
+     * @param infoB Second info
+     * @return {@code true} if external and change types are equally configured
+     */
+    private boolean equals(final ResourceChangeListenerInfo infoA, final 
ResourceChangeListenerInfo infoB) {
+        if ( infoA.isExternal() && !infoB.isExternal() ) {
+            return false;
+        }
+        if ( !infoA.isExternal() && infoB.isExternal() ) {
+            return false;
+        }
+        return 
infoA.getResourceChangeTypes().equals(infoB.getResourceChangeTypes());
     }
 
     /**
@@ -152,7 +222,24 @@ public class BasicObservationReporter im
      * @param config The configuration
      * @return The filtered list.
      */
-    private List<ResourceChange> filterChanges(final Iterable<ResourceChange> 
changes, final ListenerConfig config) {
+    private List<ResourceChange> filterChanges(final Iterable<ResourceChange> 
changes, final ObserverConfiguration config) {
+        final ResourceChangeListImpl filtered = new 
ResourceChangeListImpl(this.searchPath);
+        for (final ResourceChange c : changes) {
+            if (matches(c, config)) {
+                filtered.add(c);
+            }
+        }
+        filtered.lock();
+        return filtered;
+    }
+
+    /**
+     * Filter the change list based on the resource change listener, only type 
and external needs to be checkd.
+     * @param changes The list of changes
+     * @param config The resource change listener info
+     * @return The filtered list.
+     */
+    private List<ResourceChange> filterChanges(final Iterable<ResourceChange> 
changes, final ResourceChangeListenerInfo config) {
         final ResourceChangeListImpl filtered = new 
ResourceChangeListImpl(this.searchPath);
         for (final ResourceChange c : changes) {
             if (matches(c, config)) {
@@ -169,65 +256,35 @@ public class BasicObservationReporter im
      * @param config The configuration
      * @return {@code true} whether it matches
      */
-    private boolean matches(final ResourceChange change, final ListenerConfig 
config) {
-        if (!config.types.contains(change.getType())) {
+    private boolean matches(final ResourceChange change, final 
ObserverConfiguration config) {
+        if (!config.getChangeTypes().contains(change.getType())) {
             return false;
         }
-        if (!config.isExternal && change.isExternal()) {
+        if (!config.includeExternal() && change.isExternal()) {
             return false;
         }
-        if (config.paths.matches(change.getPath()) == null ) {
+        if (config.getPaths().matches(change.getPath()) == null ) {
+            return false;
+        }
+        if ( config.getExcludedPaths().matches(change.getPath()) != null ) {
             return false;
         }
         return true;
     }
 
-    private static final class ObserverConfig {
-        public final Set<ChangeType> types = new HashSet<ChangeType>();
-        public boolean isExternal;
-    }
-
-    private static final class ListenerConfig {
-
-        public final PathSet paths;
-
-        public final boolean isExternal;
-
-        public final Set<ChangeType> types;
-
-        public ListenerConfig(final ResourceChangeListenerInfo info, 
Set<ChangeType> types) {
-            this.paths = info.getPaths();
-            this.isExternal = info.isExternal();
-            this.types = types;
-        }
-
-        @Override
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + (isExternal ? 1231 : 1237);
-            result = prime * result + paths.hashCode();
-            result = prime * result + types.hashCode();
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            ListenerConfig other = (ListenerConfig) obj;
-            if (isExternal != other.isExternal)
-                return false;
-            if (!paths.equals(other.paths))
-                return false;
-            if (!types.equals(other.types))
-                return false;
-            return true;
+    /**
+     * Match a change against the configuration
+     * @param change The change
+     * @param config The configuration
+     * @return {@code true} whether it matches
+     */
+    private boolean matches(final ResourceChange change, final 
ResourceChangeListenerInfo config) {
+        if (!config.getResourceChangeTypes().contains(change.getType())) {
+            return false;
         }
-
+        if (!config.isExternal() && change.isExternal()) {
+            return false;
+        }
+        return true;
     }
 }

Modified: 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java?rev=1765397&r1=1765396&r2=1765397&view=diff
==============================================================================
--- 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java
 (original)
+++ 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java
 Tue Oct 18 07:36:36 2016
@@ -18,14 +18,19 @@
  */
 package org.apache.sling.resourceresolver.impl.observation;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.api.resource.path.PathSet;
 import org.apache.sling.spi.resource.provider.ObserverConfiguration;
 
+/**
+ * Implementation of a {@code ObserverConfiguration}
+ */
 public class BasicObserverConfiguration implements ObserverConfiguration {
 
     private final boolean includeExternal;
@@ -33,17 +38,23 @@ public class BasicObserverConfiguration
     private final PathSet paths;
 
     private final PathSet excludedPaths;
-    
-    private final Set<String> propertyNamesHint = new HashSet<String>();
+
+    private final Set<String> propertyNamesHint;
 
     private final Set<ChangeType> changeTypes;
 
-    public BasicObserverConfiguration(final String path, final Set<ChangeType> 
types,
-            final boolean isExternal, final PathSet excludePaths) {
+    private final List<ResourceChangeListenerInfo> listeners = new 
ArrayList<>();
+
+    public BasicObserverConfiguration(final PathSet paths,
+            final Set<ChangeType> types,
+            final boolean isExternal,
+            final PathSet excludePaths,
+            final Set<String> propertyNamesHint) {
         this.includeExternal = isExternal;
-        this.paths = PathSet.fromStrings(path);
+        this.paths = paths;
         this.changeTypes = Collections.unmodifiableSet(types);
-        this.excludedPaths = excludePaths.getSubset(path);
+        this.excludedPaths = excludePaths;
+        this.propertyNamesHint = propertyNamesHint;
     }
 
     public BasicObserverConfiguration(final PathSet set) {
@@ -54,6 +65,23 @@ public class BasicObserverConfiguration
         types.add(ChangeType.PROVIDER_REMOVED);
         this.changeTypes = Collections.unmodifiableSet(types);
         this.excludedPaths = PathSet.EMPTY_SET;
+        this.propertyNamesHint = null;
+    }
+
+    /**
+     * Add a listener
+     * @param listener The listener
+     */
+    public void addListener(final ResourceChangeListenerInfo listener) {
+        this.listeners.add(listener);
+    }
+
+    /**
+     * All listeners associated with this configuration
+     * @return List of listeners, might be empty
+     */
+    public List<ResourceChangeListenerInfo> getListeners() {
+        return this.listeners;
     }
 
     @Override
@@ -88,4 +116,57 @@ public class BasicObserverConfiguration
     public Set<String> getPropertyNamesHint() {
         return propertyNamesHint;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((changeTypes == null) ? 0 : 
changeTypes.hashCode());
+        result = prime * result + ((excludedPaths == null) ? 0 : 
excludedPaths.hashCode());
+        result = prime * result + (includeExternal ? 1231 : 1237);
+        result = prime * result + ((paths == null) ? 0 : paths.hashCode());
+        result = prime * result + ((propertyNamesHint == null) ? 0 : 
propertyNamesHint.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        BasicObserverConfiguration other = (BasicObserverConfiguration) obj;
+        if (changeTypes == null) {
+            if (other.changeTypes != null)
+                return false;
+        } else if (!changeTypes.equals(other.changeTypes))
+            return false;
+        if (excludedPaths == null) {
+            if (other.excludedPaths != null)
+                return false;
+        } else if (!excludedPaths.equals(other.excludedPaths))
+            return false;
+        if (includeExternal != other.includeExternal)
+            return false;
+        if (paths == null) {
+            if (other.paths != null)
+                return false;
+        } else if (!paths.equals(other.paths))
+            return false;
+        if (propertyNamesHint == null) {
+            if (other.propertyNamesHint != null)
+                return false;
+        } else if (!propertyNamesHint.equals(other.propertyNamesHint))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "BasicObserverConfiguration [includeExternal=" + 
includeExternal + ", paths=" + paths
+                + ", excludedPaths=" + excludedPaths + ", propertyNamesHint=" 
+ propertyNamesHint + ", changeTypes="
+                + changeTypes + ", listeners=" + listeners + "]";
+    }
 }

Modified: 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java?rev=1765397&r1=1765396&r2=1765397&view=diff
==============================================================================
--- 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java
 (original)
+++ 
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java
 Tue Oct 18 07:36:36 2016
@@ -32,7 +32,9 @@ import org.apache.sling.api.resource.Res
 import 
org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.api.resource.path.PathSet;
+import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.osgi.framework.ServiceReference;
 
 /**
@@ -50,14 +52,14 @@ public class ResourceChangeListenerInfo
 
     private final Set<ChangeType> providerChangeTypes;
 
+    private final Set<String> propertyNamesHint;
+
     private final boolean valid;
 
     private volatile boolean external = false;
 
     private volatile ResourceChangeListener listener;
 
-    private static final String GLOB_PREFIX = "glob:";
-
     public ResourceChangeListenerInfo(final 
ServiceReference<ResourceChangeListener> ref, final String[] searchPaths) {
         boolean configValid = true;
         final Set<String> pathsSet = new HashSet<String>();
@@ -66,23 +68,23 @@ public class ResourceChangeListenerInfo
             for(final String p : paths) {
                 boolean isGlobPattern = false;
                 String normalisedPath = ResourceUtil.normalize(p);
-                if (p.startsWith(GLOB_PREFIX)) {
+                if (p.startsWith(Path.GLOB_PREFIX)) {
                     isGlobPattern = true;
-                    normalisedPath =  
ResourceUtil.normalize(p.substring(GLOB_PREFIX.length()));
+                    normalisedPath =  
ResourceUtil.normalize(p.substring(Path.GLOB_PREFIX.length()));
                 }
                 if (!".".equals(p) && normalisedPath.isEmpty()) {
                     configValid = false;
                 } else if ( normalisedPath.startsWith("/") && !isGlobPattern ) 
{
                     pathsSet.add(normalisedPath);
                 } else if (normalisedPath.startsWith("/") && isGlobPattern) {
-                    pathsSet.add(GLOB_PREFIX + normalisedPath);
+                    pathsSet.add(Path.GLOB_PREFIX + normalisedPath);
                 } else {
                     for(final String sp : searchPaths) {
                         if ( p.equals(".") ) {
                             pathsSet.add(sp);
                         } else {
                             if (isGlobPattern) {
-                                pathsSet.add(GLOB_PREFIX + 
ResourceUtil.normalize(sp + normalisedPath));
+                                pathsSet.add(Path.GLOB_PREFIX + 
ResourceUtil.normalize(sp + normalisedPath));
                             } else {
                                 pathsSet.add(ResourceUtil.normalize(sp + 
normalisedPath));
                             }
@@ -147,6 +149,14 @@ public class ResourceChangeListenerInfo
             this.providerChangeTypes = DEFAULT_CHANGE_PROVIDER_TYPES;
         }
 
+        if ( ref.getProperty(ResourceChangeListener.PROPERTY_NAMES_HINT) != 
null ) {
+            this.propertyNamesHint = new HashSet<String>();
+            for(final String val : 
PropertiesUtil.toStringArray(ref.getProperty(ResourceChangeListener.PROPERTY_NAMES_HINT))
 ) {
+                this.propertyNamesHint.add(val);
+            }
+        } else {
+            this.propertyNamesHint = null;
+        }
         this.valid = configValid;
     }
 
@@ -166,6 +176,14 @@ public class ResourceChangeListenerInfo
         return this.paths;
     }
 
+    /**
+     * Return a set of property name hints
+     * @return The set of names or {@code null}.
+     */
+    public Set<String> getPropertyNamesHint() {
+        return this.propertyNamesHint;
+    }
+
     public boolean isExternal() {
         return this.external;
     }
@@ -178,4 +196,11 @@ public class ResourceChangeListenerInfo
         this.listener = listener;
         this.external = listener instanceof ExternalResourceChangeListener;
     }
+
+    @Override
+    public String toString() {
+        return "ResourceChangeListenerInfo [paths=" + paths + ", 
resourceChangeTypes=" + resourceChangeTypes
+                + ", providerChangeTypes=" + providerChangeTypes + ", 
propertyNamesHint=" + propertyNamesHint
+                + ", valid=" + valid + ", external=" + external + ", 
listener=" + listener + "]";
+    }
 }


Reply via email to