You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2019/09/07 16:04:30 UTC

[sis] branch geoapi-4.0 updated (4f46eeb -> d756a19)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.


    from 4f46eeb  Prepare replacement of WarningListeners by StoreListeners. https://issues.apache.org/jira/browse/SIS-421
     new 237a0a0  Replace the use of WarningListeners by StoreListeners in DataStore implementations. https://issues.apache.org/jira/browse/SIS-421
     new 8201633  Replace null argument by call to convenience StoreListeners.warning(…) method.
     new d756a19  Deprecate WarningListener, replaced by the more generic StoreListener. In module that do not depend on org.apache.sis.storage, use java.util.logging.Filter.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/sis/internal/jaxb/Context.java |  13 +--
 .../apache/sis/metadata/sql/MetadataSource.java    |   6 ++
 .../src/main/java/org/apache/sis/xml/Pooled.java   |  12 ++-
 .../src/main/java/org/apache/sis/xml/XML.java      |  16 +++
 .../apache/sis/metadata/iso/MarshallingTest.java   |  24 ++---
 .../metadata/iso/citation/DefaultContactTest.java  |  20 +---
 .../identification/DefaultBrowseGraphicTest.java   |  17 +---
 .../main/java/org/apache/sis/referencing/CRS.java  |  41 +++++---
 .../apache/sis/util/logging/WarningListener.java   |  17 +++-
 .../apache/sis/util/logging/WarningListeners.java  |   9 --
 .../sis/util/logging/EmptyWarningListeners.java    |  71 --------------
 .../sis/util/logging/WarningListenersTest.java     |   1 +
 .../storage/earthobservation/LandsatReader.java    |  13 ++-
 .../sis/storage/earthobservation/LandsatStore.java |  24 ++---
 .../earthobservation/LandsatReaderTest.java        |   7 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  34 +++----
 .../sis/storage/geotiff/ImageFileDirectory.java    |   2 +-
 .../org/apache/sis/internal/netcdf/Decoder.java    |   7 +-
 .../sis/internal/netcdf/DiscreteSampling.java      |   7 +-
 .../apache/sis/internal/netcdf/NamedElement.java   |   4 +-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  13 ++-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |   6 +-
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  15 ++-
 .../sis/internal/netcdf/ucar/FeaturesWrapper.java  |   5 +-
 .../sis/internal/netcdf/ucar/LogAdapter.java       |  11 +--
 .../apache/sis/storage/netcdf/MetadataReader.java  |   9 +-
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |  24 ++---
 .../sis/storage/netcdf/NetcdfStoreProvider.java    |   8 +-
 .../org/apache/sis/internal/netcdf/TestCase.java   |  11 +--
 .../internal/netcdf/impl/ChannelDecoderTest.java   |   3 +-
 .../storage/netcdf/NetcdfStoreProviderTest.java    |   9 +-
 .../apache/sis/internal/sql/feature/Analyzer.java  |   7 +-
 .../apache/sis/internal/sql/feature/Database.java  |   5 +-
 .../java/org/apache/sis/storage/sql/SQLStore.java  |  24 ++---
 .../sis/internal/storage/AbstractFeatureSet.java   |  22 ++---
 .../sis/internal/storage/AbstractGridResource.java |  22 +----
 .../sis/internal/storage/AbstractResource.java     |  89 +++++------------
 .../sis/internal/storage/AggregatedFeatureSet.java |  32 +-----
 .../internal/storage/ConcatenatedFeatureSet.java   |  23 +++--
 .../internal/storage/DocumentedStoreProvider.java  |   7 +-
 .../sis/internal/storage/JoinFeatureSet.java       |   9 +-
 .../sis/internal/storage/MemoryFeatureSet.java     |  11 +--
 .../apache/sis/internal/storage/URIDataStore.java  |  25 ++---
 .../org/apache/sis/internal/storage/csv/Store.java |   4 +-
 .../apache/sis/internal/storage/folder/Store.java  |  26 ++---
 .../sis/internal/storage/io/ChannelFactory.java    |  33 +++----
 .../sis/internal/storage/query/FeatureSubset.java  |  13 +--
 .../sis/internal/storage/wkt/StoreFormat.java      |  16 ++-
 .../org/apache/sis/internal/storage/xml/Store.java |  39 ++++----
 .../java/org/apache/sis/storage/DataStore.java     |  82 ++++++++++++++--
 .../org/apache/sis/storage/event/StoreEvent.java   |  10 +-
 .../apache/sis/storage/event/StoreListeners.java   |  90 ++++++++++++++---
 .../internal/storage/AbstractGridResourceTest.java |   3 -
 .../java/org/apache/sis/storage/DataStoreMock.java |  40 ++++++--
 .../sis/storage/event/StoreListenersTest.java      | 109 +++++++++++++++++++++
 .../apache/sis/test/suite/StorageTestSuite.java    |   1 +
 .../internal/storage/xml/stream/StaxDataStore.java |  29 ++----
 57 files changed, 631 insertions(+), 599 deletions(-)
 delete mode 100644 core/sis-utility/src/test/java/org/apache/sis/util/logging/EmptyWarningListeners.java
 create mode 100644 storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java


[sis] 03/03: Deprecate WarningListener, replaced by the more generic StoreListener. In module that do not depend on org.apache.sis.storage, use java.util.logging.Filter.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit d756a195f427e468ac9770b925b090f543a02155
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Sep 7 16:46:30 2019 +0200

    Deprecate WarningListener, replaced by the more generic StoreListener.
    In module that do not depend on org.apache.sis.storage, use java.util.logging.Filter.
---
 .../java/org/apache/sis/internal/jaxb/Context.java | 13 +++----
 .../apache/sis/metadata/sql/MetadataSource.java    |  6 ++++
 .../src/main/java/org/apache/sis/xml/Pooled.java   | 12 +++++--
 .../src/main/java/org/apache/sis/xml/XML.java      | 16 +++++++++
 .../apache/sis/metadata/iso/MarshallingTest.java   | 24 ++++---------
 .../metadata/iso/citation/DefaultContactTest.java  | 20 +++--------
 .../identification/DefaultBrowseGraphicTest.java   | 17 +++------
 .../main/java/org/apache/sis/referencing/CRS.java  | 41 +++++++++++++++-------
 .../apache/sis/util/logging/WarningListener.java   | 17 ++++++++-
 .../apache/sis/util/logging/WarningListeners.java  |  8 -----
 .../org/apache/sis/internal/storage/xml/Store.java | 34 ++++++++++--------
 .../internal/storage/xml/stream/StaxDataStore.java | 25 +++++--------
 12 files changed, 126 insertions(+), 107 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/Context.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/Context.java
index 5262688..a090b8d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/Context.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/Context.java
@@ -26,10 +26,10 @@ import java.util.LinkedList;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
+import java.util.logging.Filter;
 import org.apache.sis.util.Version;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.resources.IndexedResourceBundle;
@@ -181,7 +181,7 @@ public final class Context extends MarshalContext {
     /**
      * The object to inform about warnings, or {@code null} if none.
      */
-    private final WarningListener<?> warningListener;
+    private final Filter warningListener;
 
     /**
      * The {@code <gml:*PropertyType>} which is wrapping the {@code <gml:*Type>} object to (un)marshal, or
@@ -233,7 +233,7 @@ public final class Context extends MarshalContext {
                    final Version            versionMetadata,
                    final ReferenceResolver  resolver,
                    final ValueConverter     converter,
-                   final WarningListener<?> warningListener)
+                   final Filter             warningListener)
     {
         if (versionMetadata != null && versionMetadata.compareTo(LegacyNamespaces.VERSION_2014) < 0) {
             bitMasks |= LEGACY_METADATA;
@@ -621,11 +621,12 @@ public final class Context extends MarshalContext {
         record.setSourceMethodName(method);
         record.setLoggerName(Loggers.XML);
         if (context != null) {
-            final WarningListener<?> warningListener = context.warningListener;
+            final Filter warningListener = context.warningListener;
             if (warningListener != null) {
                 record.setThrown(exception);
-                warningListener.warningOccured(null, record);
-                return;
+                if (!warningListener.isLoggable(record)) {
+                    return;
+                }
             }
         }
         /*
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
index 16396c1..5e4a6c2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
@@ -1100,7 +1100,10 @@ public class MetadataSource implements AutoCloseable {
      *
      * @param  listener  the listener to add.
      * @throws IllegalArgumentException if the given listener is already registered in this metadata source.
+     *
+     * @deprecated {@code WarningListener} to be replaced by {@link java.util.logging.Filter}.
      */
+    @Deprecated
     public void addWarningListener(final WarningListener<? super MetadataSource> listener)
             throws IllegalArgumentException
     {
@@ -1112,7 +1115,10 @@ public class MetadataSource implements AutoCloseable {
      *
      * @param  listener  the listener to remove.
      * @throws NoSuchElementException if the given listener is not registered in this metadata source.
+     *
+     * @deprecated {@code WarningListener} to be replaced by {@link java.util.logging.Filter}.
      */
+    @Deprecated
     public void removeWarningListener(final WarningListener<? super MetadataSource> listener)
             throws NoSuchElementException
     {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/Pooled.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/Pooled.java
index 26c40c4..4d0bcef 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/Pooled.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/Pooled.java
@@ -24,6 +24,7 @@ import java.util.IllformedLocaleException;
 import java.util.Locale;
 import java.util.TimeZone;
 import java.util.function.UnaryOperator;
+import java.util.logging.Filter;
 import javax.xml.validation.Schema;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.JAXBException;
@@ -153,7 +154,7 @@ abstract class Pooled {
     /**
      * The object to inform about warnings, or {@code null} if none.
      */
-    private WarningListener<?> warningListener;
+    private Filter warningListener;
 
     /**
      * The {@link System#nanoTime()} value of the last call to {@link #reset(Pooled)}.
@@ -378,8 +379,12 @@ abstract class Pooled {
                     }
                     return;
                 }
+                case XML.WARNING_FILTER: {
+                    warningListener = (Filter) value;
+                    return;
+                }
                 case XML.WARNING_LISTENER: {
-                    warningListener = (WarningListener<?>) value;
+                    warningListener = ((WarningListener<?>) value).asFilter();
                     return;
                 }
                 case TypeRegistration.ROOT_ADAPTERS: {
@@ -420,7 +425,8 @@ abstract class Pooled {
             case XML.METADATA_VERSION:  return versionMetadata;
             case XML.RESOLVER:          return resolver;
             case XML.CONVERTER:         return converter;
-            case XML.WARNING_LISTENER:  return warningListener;
+            case XML.WARNING_FILTER:    return warningListener;
+            case XML.WARNING_LISTENER:  return null;
             case XML.LENIENT_UNMARSHAL: return (bitMasks & Context.LENIENT_UNMARSHAL) != 0;
             case XML.STRING_SUBSTITUTES: {
                 int n = 0;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/XML.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/XML.java
index 1f0255c..25d5411 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/XML.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/XML.java
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.Locale;
 import java.util.TimeZone;
 import java.util.Collections;
+import java.util.logging.Filter;
 import java.util.logging.LogRecord;             // For javadoc
 import java.net.URL;
 import java.io.File;
@@ -361,6 +362,18 @@ public final class XML extends Static {
 
     /**
      * Specifies a listener to be notified when a non-fatal error occurred during the (un)marshalling.
+     * The value for this property shall be an instance of {@link Filter}.
+     *
+     * <p>By default, warnings that occur during the (un)marshalling process are logged. However if a
+     * property is set for this key, then the {@link Filter#isLoggable(LogRecord)} method will be invoked.
+     * If that method returns {@code false}, then the warning will not be logged by the (un)marshaller.</p>
+     *
+     * @since 1.0
+     */
+    public static final String WARNING_FILTER = "org.apache.sis.xml.warningFilter";
+
+    /**
+     * Specifies a listener to be notified when a non-fatal error occurred during the (un)marshalling.
      * The value for this property shall be an instance of {@code WarningListener<Object>}.
      *
      * <p>By default, warnings that occur during the (un)marshalling process are logged. However if a
@@ -368,7 +381,10 @@ public final class XML extends Static {
      * method will be invoked and the warning will <em>not</em> be logged by the (un)marshaller.</p>
      *
      * @see WarningListener
+     *
+     * @deprecated Replaced by {@link #WARNING_FILTER}.
      */
+    @Deprecated
     public static final String WARNING_LISTENER = "org.apache.sis.xml.warningListener";
 
     /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java
index 7492eb4..8eda873 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/MarshallingTest.java
@@ -23,6 +23,7 @@ import java.util.Map;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import java.util.MissingResourceException;
 import java.net.URI;
@@ -54,7 +55,6 @@ import org.apache.sis.metadata.iso.spatial.*;
 import org.apache.sis.util.iso.DefaultRecordSchema;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.iso.DefaultInternationalString;
-import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.measure.Units;
 import org.apache.sis.xml.XML;
 import org.apache.sis.xml.NilReason;
@@ -82,7 +82,7 @@ import static org.junit.Assert.*;
  * @since 1.0
  * @module
  */
-public final class MarshallingTest extends TestUsingFile implements WarningListener<Object> {
+public final class MarshallingTest extends TestUsingFile implements Filter {
     /**
      * The marshaller used to handle marshalling the created DefaultMetadata object.
      */
@@ -113,7 +113,7 @@ public final class MarshallingTest extends TestUsingFile implements WarningListe
         output     = new StringWriter();
         pool       = getMarshallerPool();
         marshaller = pool.acquireMarshaller();
-        marshaller.setProperty(XML.WARNING_LISTENER, this);
+        marshaller.setProperty(XML.WARNING_FILTER, this);
     }
 
     /**
@@ -643,28 +643,17 @@ public final class MarshallingTest extends TestUsingFile implements WarningListe
     }
 
     /**
-     * For internal {@code DefaultMetadata} usage.
-     *
-     * @return {@code Object.class}.
-     */
-    @Override
-    public Class<Object> getSourceClass() {
-        return Object.class;
-    }
-
-    /**
      * Invoked when a warning occurred while marshalling a test XML fragment. Expected warnings are
      * "Can't find resource for bundle {@code java.util.PropertyResourceBundle}, key <cite>Foo</cite>".
      * When marshalling legacy XML only, additional warnings may occur.
      *
-     * @param source  ignored.
-     * @param warning the warning.
+     * @param  warning  the warning.
      */
     @Override
-    public void warningOccured(final Object source, final LogRecord warning) {
+    public boolean isLoggable(final LogRecord warning) {
         if (warning.getThrown() instanceof MissingResourceException) {
             assertNull("Expected a warning message without parameters.", warning.getParameters());
-            return;
+            return false;
         }
         final String message = warning.getMessage();
         if (legacyXML) {
@@ -673,5 +662,6 @@ public final class MarshallingTest extends TestUsingFile implements WarningListe
         } else {
             fail("Unexpected logging message: " + message);
         }
+        return false;
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
index ac3af11..e4c0dbe 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
@@ -18,11 +18,11 @@ package org.apache.sis.metadata.iso.citation;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import org.opengis.metadata.citation.Telephone;
 import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.internal.jaxb.Context;
-import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.xml.TestCase;
 import org.junit.Test;
@@ -40,7 +40,7 @@ import static org.junit.Assert.*;
  * @since   0.5
  * @module
  */
-public final strictfp class DefaultContactTest extends TestCase implements WarningListener<Object> {
+public final strictfp class DefaultContactTest extends TestCase implements Filter {
     /**
      * The resource key for the message of the warning that occurred, or {@code null} if none.
      */
@@ -52,29 +52,19 @@ public final strictfp class DefaultContactTest extends TestCase implements Warni
     private Object[] parameters;
 
     /**
-     * For internal {@code DefaultContact} usage.
-     *
-     * @return {@code Object.class}.
-     */
-    @Override
-    public Class<Object> getSourceClass() {
-        return Object.class;
-    }
-
-    /**
      * Invoked when a warning occurred while unmarshalling a test XML fragment. This method ensures that no other
      * warning occurred before this method call (i.e. each test is allowed to cause at most one warning), then
      * remember the warning parameters for verification by the test method.
      *
-     * @param source   ignored.
-     * @param warning  the warning.
+     * @param  warning  the warning.
      */
     @Override
-    public void warningOccured(final Object source, final LogRecord warning) {
+    public boolean isLoggable(final LogRecord warning) {
         assertNull(resourceKey);
         assertNull(parameters);
         assertNotNull(resourceKey = warning.getMessage());
         assertNotNull(parameters  = warning.getParameters());
+        return false;
     }
 
     /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphicTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphicTest.java
index 3bc89df..d353dd4 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphicTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphicTest.java
@@ -18,12 +18,12 @@ package org.apache.sis.metadata.iso.identification;
 
 import java.net.URI;
 import java.io.StringReader;
+import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.JAXBException;
 import javax.xml.transform.stream.StreamSource;
 import org.opengis.metadata.identification.BrowseGraphic;
-import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.Version;
 import org.apache.sis.xml.MarshallerPool;
@@ -287,26 +287,18 @@ public final strictfp class DefaultBrowseGraphicTest extends TestCase {
     /**
      * A warning listener to be registered by {@link #testWarnings()}.
      */
-    private static final class Warning implements WarningListener<Object> {
+    private static final class Warning implements Filter {
         /**
          * {@code true} if a warning has been sent by the XML unmarshaller.
          */
         boolean receivedWarning;
 
         /**
-         * Fixed to {@code Object.class} as required by {@link XML#WARNING_LISTENER} contract.
-         */
-        @Override
-        public Class<Object> getSourceClass() {
-            return Object.class;
-        }
-
-        /**
          * Invoked when a warning occurred. Ensures that no warning were previously sent,
          * then ensure that the warning content the expected message.
          */
         @Override
-        public void warningOccured(final Object source, final LogRecord warning) {
+        public boolean isLoggable(final LogRecord warning) {
             assertFalse("No other warning were expected.", receivedWarning);
             if (VERBOSE) {
                 /*
@@ -318,6 +310,7 @@ public final strictfp class DefaultBrowseGraphicTest extends TestCase {
             assertArrayEquals("FileName shall have precedence over CharacterString.",
                     new Object[] {"CharacterString", "FileName"}, warning.getParameters());
             receivedWarning = true;
+            return false;
         }
 
         /**
@@ -325,7 +318,7 @@ public final strictfp class DefaultBrowseGraphicTest extends TestCase {
          */
         public DefaultBrowseGraphic unmarshal(final String xml) throws JAXBException {
             return (DefaultBrowseGraphic) XML.unmarshal(new StreamSource(new StringReader(xml)),
-                    singletonMap(XML.WARNING_LISTENER, this));
+                    singletonMap(XML.WARNING_FILTER, this));
         }
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
index 1b9b49d..7a7b564 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
@@ -392,34 +393,38 @@ public final class CRS extends Static {
      *   <li>Otherwise this method silently returns the given CRS as-is.</li>
      * </ul>
      *
-     * <b>Note:</b> the warnings emitted by this method are redundant with the warnings emitted by
-     * {@link #fromWKT(String)} and {@link #fromXML(String)}, so the {@code warnings} argument should be {@code null}
-     * when {@code fromAuthority(…)} is invoked for the CRS parsed by one of above-mentioned methods.
-     * A non-null {@code warnings} argument is more useful for CRS parsed by {@link org.apache.sis.io.wkt.WKTFormat}
-     * or {@link org.apache.sis.xml.XML#unmarshal(String)} for instance.
-     *
-     * @param  crs       the CRS to replace by an authoritative CRS, or {@code null}.
-     * @param  factory   the factory where to search for authoritative definitions, or {@code null} for the default.
-     * @param  listener  where to send warnings, or {@code null} for ignoring warnings.
+     * <div class="section">Avoiding warning redundancies</div>
+     * The warnings logged by this method are redundant with warnings logged by other methods in this class,
+     * in particular {@link #fromWKT(String)} and {@link #fromXML(String)} methods. For avoiding this annoyance,
+     * a {@code null} value for the {@code warningFilter} argument means to shut off those redundant loggings.
+     * A non-null {@code warningFilter} argument is more useful for CRS parsed by methods outside this class,
+     * for example {@link org.apache.sis.io.wkt.WKTFormat} or {@link org.apache.sis.xml.XML#unmarshal(String)}.
+     *
+     * @param  crs            the CRS to replace by an authoritative CRS, or {@code null}.
+     * @param  factory        the factory where to search for authoritative definitions, or {@code null} for the default.
+     * @param  warningFilter  whether to log warnings, or {@code null} for the default behavior (which is to filter out
+     *                        the warnings that are redundant with warnings emitted by other methods in this class).
      * @return the suggested CRS to use (may be the {@code crs} argument itself), or {@code null} if the given CRS was null.
      * @throws FactoryException if an error occurred while querying the authority factory.
      *
-     * @since 0.8
+     * @since 1.0
      */
     public static CoordinateReferenceSystem fromAuthority(CoordinateReferenceSystem crs,
-            final CRSAuthorityFactory factory, final WarningListener<?> listener) throws FactoryException
+            final CRSAuthorityFactory factory, final Filter warningFilter) throws FactoryException
     {
         if (crs != null) {
             final DefinitionVerifier verification = DefinitionVerifier.withAuthority(crs, factory, true);
             if (verification != null) {
                 crs = verification.authoritative;
-                if (listener != null) {
+                if (warningFilter != null) {
                     final LogRecord record = verification.warning(false);
                     if (record != null) {
                         record.setLoggerName(Modules.REFERENCING);
                         record.setSourceClassName(CRS.class.getName());
                         record.setSourceMethodName("fromAuthority");
-                        listener.warningOccured(null, record);
+                        if (warningFilter.isLoggable(record)) {
+                            Logging.getLogger(Modules.REFERENCING).log(record);
+                        }
                     }
                 }
             }
@@ -428,6 +433,16 @@ public final class CRS extends Static {
     }
 
     /**
+     * @deprecated {@code WarningListener} argument replaced by {@code java.util.logging.Filter}.
+     */
+    @Deprecated
+    public static CoordinateReferenceSystem fromAuthority(CoordinateReferenceSystem crs,
+            final CRSAuthorityFactory factory, final WarningListener<?> listener) throws FactoryException
+    {
+        return fromAuthority(crs, factory, (listener != null) ? listener.asFilter() : null);
+    }
+
+    /**
      * Suggests a coordinate reference system which could be a common target for coordinate operations having the
      * given sources. This method compares the {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem)
      * domain of validity} of all given CRSs. If a CRS has a domain of validity that contains the domain of all other
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListener.java b/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListener.java
index 17c9562..a97777a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListener.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListener.java
@@ -19,6 +19,7 @@ package org.apache.sis.util.logging;
 import java.util.EventListener;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
+import java.util.logging.Filter;
 
 
 /**
@@ -36,7 +37,7 @@ import java.util.logging.LogRecord;
  * in addition to the log record.</div>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
+ * @version 1.0
  *
  * @param <S>  the base type of objects that emit warnings (the <cite>source</cite>).
  *
@@ -46,7 +47,10 @@ import java.util.logging.LogRecord;
  *
  * @since 0.3
  * @module
+ *
+ * @deprecated Replaced by {@link java.util.logging.Filter}.
  */
+@Deprecated
 public interface WarningListener<S> extends EventListener {
     /**
      * Returns the type of objects that emit warnings of interest for this listener.
@@ -75,4 +79,15 @@ public interface WarningListener<S> extends EventListener {
      * @param warning  the warning message together with programmatic information.
      */
     void warningOccured(S source, LogRecord warning);
+
+    /**
+     * Wraps this listener in a {@code java.util.logging.Filter} for transitioning
+     * to the replacement of this interface.
+     */
+    default Filter asFilter() {
+        return (LogRecord warning) -> {
+            warningOccured(null, warning);
+            return false;
+        };
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java b/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java
index 3f22af8..cf51192 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java
@@ -85,14 +85,6 @@ public class WarningListeners<S> implements Localized {
     private WarningListener<? super S>[] listeners;
 
     /**
-     * Creates a new instance without source. This constructor is for {@code EmptyWarningListeners}
-     * usage only, because it requires some method to be overloaded.
-     */
-    WarningListeners() {
-        source = null;
-    }
-
-    /**
      * Creates a new instance with initially no listener.
      * Warnings will be logger to the destination given by {@link #getLogger()},
      * unless at least one listener is {@linkplain #addWarningListener registered}.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
index 2cb658d..94504d7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
@@ -18,6 +18,7 @@ package org.apache.sis.internal.storage.xml;
 
 import java.util.Map;
 import java.util.Collections;
+import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import java.io.Closeable;
 import java.io.Reader;
@@ -35,7 +36,6 @@ import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.UnsupportedStorageException;
 import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.storage.URIDataStore;
@@ -57,11 +57,11 @@ import org.apache.sis.setup.OptionKey;
  * The above list may be extended in any future SIS version.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.4
  * @module
  */
-final class Store extends URIDataStore {
+final class Store extends URIDataStore implements Filter {
     /**
      * The input stream or reader, set by the constructor and cleared when no longer needed.
      */
@@ -123,23 +123,27 @@ final class Store extends URIDataStore {
      */
     private Map<String,?> properties() {
         if (listeners.hasListeners(WarningEvent.class)) {
-            return Collections.singletonMap(XML.WARNING_LISTENER, new WarningListener<Object>() {
-                /** Returns the type of objects that emit warnings of interest for this listener. */
-                @Override public Class<Object> getSourceClass() {
-                    return Object.class;
-                }
-
-                /** Reports the occurrence of a non-fatal error during XML unmarshalling. */
-                @Override public void warningOccured(final Object source, final LogRecord warning) {
-                    warning.setLoggerName(Loggers.XML);
-                    listeners.warning(warning);
-                }
-            });
+            return Collections.singletonMap(XML.WARNING_FILTER, this);
         }
         return null;
     }
 
     /**
+     * Intercepts warnings produced during the (un)marshalling process and redirect them to the listeners.
+     * This method is public as an implementation convenience for {@link #properties()} method;
+     * it should not be invoked directly.
+     *
+     * @param  warning  the warning that occurred during (un)marshalling.
+     * @return always {@code false} since logging will be handled by {@code listeners}.
+     */
+    @Override
+    public boolean isLoggable(final LogRecord warning) {
+        warning.setLoggerName(Loggers.XML);
+        listeners.warning(warning);
+        return false;
+    }
+
+    /**
      * Unmarshal the object, if not already done. Note that {@link #object} may still be null
      * if an exception has been thrown at this invocation time or in previous invocation.
      *
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
index 299e6b4..9dc4462 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
@@ -20,6 +20,7 @@ import java.util.Locale;
 import java.util.TimeZone;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
+import java.util.logging.Filter;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
@@ -52,7 +53,6 @@ import org.apache.sis.storage.ConcurrentWriteException;
 import org.apache.sis.storage.DataStoreClosedException;
 import org.apache.sis.storage.ForwardOnlyStorageException;
 import org.apache.sis.storage.UnsupportedStorageException;
-import org.apache.sis.util.logging.WarningListener;
 
 
 /**
@@ -288,7 +288,7 @@ public abstract class StaxDataStore extends URIDataStore {
      * Holds information that can be used for (un)marshallers configuration, and opportunistically
      * implement various listeners used by JAXB (actually the SIS wrappers) or StAX.
      */
-    private final class Config extends AbstractMap<String,Object> implements XMLReporter, WarningListener<Object> {
+    private final class Config extends AbstractMap<String,Object> implements XMLReporter, Filter {
         /**
          * Fetches configuration information from the given object.
          */
@@ -306,9 +306,9 @@ public abstract class StaxDataStore extends URIDataStore {
         public Object get(final Object key) {
             if (key instanceof String) {
                 switch ((String) key) {
-                    case XML.LOCALE:           return locale;
-                    case XML.TIMEZONE:         return timezone;
-                    case XML.WARNING_LISTENER: return this;
+                    case XML.LOCALE:         return locale;
+                    case XML.TIMEZONE:       return timezone;
+                    case XML.WARNING_FILTER: return this;
                 }
             }
             return null;
@@ -319,7 +319,7 @@ public abstract class StaxDataStore extends URIDataStore {
          */
         @Override
         protected EntryIterator<String, Object> entryIterator() {
-            return new KeyIterator(XML.LOCALE, XML.TIMEZONE, XML.WARNING_LISTENER);
+            return new KeyIterator(XML.LOCALE, XML.TIMEZONE, XML.WARNING_FILTER);
         }
 
         /**
@@ -342,22 +342,13 @@ public abstract class StaxDataStore extends URIDataStore {
         /**
          * Reports a warning represented by the given log record.
          *
-         * @param source   ignored (typically a JAXB object being unmarshalled). Can be {@code null}.
          * @param warning  the warning as a log record.
          */
         @Override
-        public void warningOccured(final Object source, final LogRecord warning) {
+        public boolean isLoggable(final LogRecord warning) {
             warning.setLoggerName(null);        // For allowing 'listeners' to select a logger name.
             listeners.warning(warning);
-        }
-
-        /**
-         * Returns the type of objects that emit warnings of interest for this listener.
-         * Fixed to {@code Object.class} as required by {@link org.apache.sis.xml.XML#WARNING_LISTENER} documentation.
-         */
-        @Override
-        public final Class<Object> getSourceClass() {
-            return Object.class;
+            return false;
         }
 
         /**


[sis] 02/03: Replace null argument by call to convenience StoreListeners.warning(…) method.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 8201633d64b14b6869e8fc2f97679d5846c3aaa5
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Sep 7 15:16:41 2019 +0200

    Replace null argument by call to convenience StoreListeners.warning(…) method.
---
 .../org/apache/sis/storage/earthobservation/LandsatReader.java    | 6 +++---
 .../main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java    | 2 +-
 .../java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java  | 4 ++--
 .../java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java    | 6 +++---
 .../java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java  | 8 ++++----
 .../main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java | 2 +-
 .../main/java/org/apache/sis/storage/netcdf/MetadataReader.java   | 2 +-
 .../org/apache/sis/internal/storage/DocumentedStoreProvider.java  | 2 +-
 .../src/main/java/org/apache/sis/internal/storage/csv/Store.java  | 4 ++--
 .../main/java/org/apache/sis/internal/storage/folder/Store.java   | 2 +-
 .../java/org/apache/sis/internal/storage/wkt/StoreFormat.java     | 2 +-
 .../src/main/java/org/apache/sis/internal/storage/xml/Store.java  | 2 +-
 .../org/apache/sis/internal/storage/xml/stream/StaxDataStore.java | 2 +-
 13 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index 333e6d2..386b4b7 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -372,7 +372,7 @@ final class LandsatReader extends MetadataBuilder {
                 }
             }
         }
-        listeners.warning(errors().getString(Errors.Keys.UnexpectedEndOfFile_1, getFilename()), null);
+        listeners.warning(errors().getString(Errors.Keys.UnexpectedEndOfFile_1, getFilename()));
     }
 
     /**
@@ -804,7 +804,7 @@ final class LandsatReader extends MetadataBuilder {
      */
     private DefaultBand band(final String key, int index) {
         if (index < 1 || index > BAND_NAMES.length) {
-            listeners.warning(errors().getString(Errors.Keys.UnexpectedValueInElement_2, key + index, index), null);
+            listeners.warning(errors().getString(Errors.Keys.UnexpectedValueInElement_2, key + index, index));
             return null;
         }
         DefaultBand band = bands[--index];
@@ -830,7 +830,7 @@ final class LandsatReader extends MetadataBuilder {
         if (projection != null) {
             projection.parameter(name).setValue(Double.parseDouble(value), isLinear ? Units.METRE : Units.DEGREE);
         } else {
-            listeners.warning(errors().getString(Errors.Keys.UnexpectedProperty_2, filename, key), null);
+            listeners.warning(errors().getString(Errors.Keys.UnexpectedProperty_2, filename, key));
         }
     }
 
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 1689296..ecbc331 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -198,7 +198,7 @@ public class GeoTiffStore extends DataStore implements Aggregate {
                 builder.setFormat(Constants.GEOTIFF);
             } catch (MetadataStoreException e) {
                 builder.addFormatName(Constants.GEOTIFF);
-                warning(null, e);
+                listeners.warning(e);
             }
             builder.addEncoding(encoding, MetadataBuilder.Scope.METADATA);
             builder.addResourceScope(ScopeCode.COVERAGE, null);
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index 32f876d..c7e5481 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -848,7 +848,7 @@ public final class ChannelDecoder extends Decoder {
         if (value instanceof CharSequence) try {
             return StandardDateFormat.toDate(StandardDateFormat.FORMAT.parse((CharSequence) value));
         } catch (DateTimeException | ArithmeticException e) {
-            listeners.warning(null, e);
+            listeners.warning(e);
         }
         return null;
     }
@@ -874,7 +874,7 @@ public final class ChannelDecoder extends Decoder {
                 }
             }
         } catch (IncommensurableException | ParserException | DateTimeException | ArithmeticException e) {
-            listeners.warning(null, e);
+            listeners.warning(e);
         }
         return dates;
     }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
index 1648df0..8f30f9d 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
@@ -193,7 +193,7 @@ search: for (final VariableInfo counts : decoder.variables) {
                     final DimensionInfo sampleDimension = decoder.findDimension(sampleDimName);
                     if (sampleDimension == null) {
                         decoder.listeners.warning(decoder.resources().getString(Resources.Keys.DimensionNotFound_3,
-                                decoder.getFilename(), counts.getName(), sampleDimName), null);
+                                decoder.getFilename(), counts.getName(), sampleDimName));
                         continue;
                     }
                     /*
@@ -222,7 +222,7 @@ search: for (final VariableInfo counts : decoder.variables) {
                         }
                         if (identifiers == null) {
                             decoder.listeners.warning(decoder.resources().getString(Resources.Keys.VariableNotFound_2,
-                                    decoder.getFilename(), featureDimension.name), null);
+                                    decoder.getFilename(), featureDimension.name));
                             continue;
                         }
                     }
@@ -241,7 +241,7 @@ search: for (final VariableInfo counts : decoder.variables) {
                             decoder.listeners.warning(decoder.resources().getString(
                                     Resources.Keys.UnexpectedDimensionForVariable_4,
                                     decoder.getFilename(), identifiers.getName(),
-                                    featureDimension.getName(), identifiers.dimensions[i].name), null);
+                                    featureDimension.getName(), identifiers.dimensions[i].name));
                             continue search;
                         }
                     }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
index 419af42..d9c6306 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
@@ -319,7 +319,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
                         try {
                             date = CalendarDateFormatter.isoStringToCalendarDate(Calendar.proleptic_gregorian, value);
                         } catch (IllegalArgumentException e) {
-                            listeners.warning(null, e);
+                            listeners.warning(e);
                             continue;
                         }
                         return new Date(date.getMillis());
@@ -343,8 +343,8 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
         final DateUnit unit;
         try {
             unit = new DateUnit(symbol);
-        } catch (Exception e) { // Declared by the DateUnit constructor.
-            listeners.warning(null, e);
+        } catch (Exception e) {                 // Declared by the DateUnit constructor.
+            listeners.warning(e);
             return dates;
         }
         for (int i=0; i<values.length; i++) {
@@ -527,7 +527,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
      */
     @Override
     public void setError(final String message) {
-        listeners.warning(message, null);
+        listeners.warning(message);
     }
 
     /**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java
index 97c0d7b..3f9451b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java
@@ -117,7 +117,7 @@ final class LogAdapter implements Appendable {
     private void warning(CharSequence message) {
         message = CharSequences.trimWhitespaces(message);
         if (message.length() != 0) {
-            listeners.warning(message.toString(), null);
+            listeners.warning(message.toString());
         }
     }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 8d1a4c7..4b1c6c7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -197,7 +197,7 @@ final class MetadataReader extends MetadataBuilder {
      * or logs the record otherwise.
      */
     private void warning(final Exception e) {
-        decoder.listeners.warning(null, e);
+        decoder.listeners.warning(e);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java
index 6170f40..3e27da3 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java
@@ -96,7 +96,7 @@ public abstract class DocumentedStoreProvider extends URIDataStore.Provider {
             return MetadataSource.getProvided().lookup(Format.class, name);
         } catch (MetadataStoreException e) {
             if (listeners != null) {
-                listeners.warning(null, e);
+                listeners.warning(e);
             } else {
                 final Level level;
                 if (!logged) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
index 51ce839..aae37b3 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
@@ -646,7 +646,7 @@ final class Store extends URIDataStore implements FeatureSet {
                 builder.setFormat(format);
             } catch (MetadataStoreException e) {
                 builder.addFormatName(format);
-                listeners.warning(null, e);
+                listeners.warning(e);
             }
             builder.addEncoding(encoding, MetadataBuilder.Scope.ALL);
             builder.addResourceScope(ScopeCode.DATASET, null);
@@ -659,7 +659,7 @@ final class Store extends URIDataStore implements FeatureSet {
                  * Failed to set the temporal components if the sis-temporal module was
                  * not on the classpath, but the other dimensions still have been set.
                  */
-                listeners.warning(null, e);
+                listeners.warning(e);
             }
             builder.addFeatureType(featureType, -1);
             addTitleOrIdentifier(builder);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index 6b42068..86582c1 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -383,7 +383,7 @@ class Store extends DataStore implements StoreResource, Aggregate, DirectoryStre
     private void sharedRepository(final Path candidate) {
         if (!sharedRepositoryReported) {
             sharedRepositoryReported = true;
-            listeners.warning(message(Resources.Keys.SharedDirectory_1, candidate), null);
+            listeners.warning(message(Resources.Keys.SharedDirectory_1, candidate));
         }
     }
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
index d7342f0..811e595 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
@@ -143,7 +143,7 @@ public final class StoreFormat extends WKTFormat {
                 if (warning != null) log(warning);
             }
         } catch (FactoryException e) {
-            listeners.warning(null, e);
+            listeners.warning(e);
         }
     }
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
index 4000afa..2cb658d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
@@ -164,7 +164,7 @@ final class Store extends URIDataStore {
                 log(v.warning(false));
             }
         } catch (FactoryException e) {
-            listeners.warning(null, e);
+            listeners.warning(e);
         }
     }
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
index e7d59e5..299e6b4 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
@@ -279,7 +279,7 @@ public abstract class StaxDataStore extends URIDataStore {
                 return true;
             }
         } catch (InvalidSeekException e) {
-            listeners.warning(null, e);
+            listeners.warning(e);
         }
         return false;
     }


[sis] 01/03: Replace the use of WarningListeners by StoreListeners in DataStore implementations. https://issues.apache.org/jira/browse/SIS-421

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 237a0a028b0078cb28f4b97e72dc730300051c43
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Sep 7 15:03:11 2019 +0200

    Replace the use of WarningListeners by StoreListeners in DataStore implementations.
    https://issues.apache.org/jira/browse/SIS-421
---
 .../apache/sis/util/logging/WarningListeners.java  |   1 -
 .../sis/util/logging/EmptyWarningListeners.java    |  71 --------------
 .../sis/util/logging/WarningListenersTest.java     |   1 +
 .../storage/earthobservation/LandsatReader.java    |   7 +-
 .../sis/storage/earthobservation/LandsatStore.java |  24 ++---
 .../earthobservation/LandsatReaderTest.java        |   7 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  32 +++---
 .../sis/storage/geotiff/ImageFileDirectory.java    |   2 +-
 .../org/apache/sis/internal/netcdf/Decoder.java    |   7 +-
 .../sis/internal/netcdf/DiscreteSampling.java      |   7 +-
 .../apache/sis/internal/netcdf/NamedElement.java   |   4 +-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |   9 +-
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |   7 +-
 .../sis/internal/netcdf/ucar/FeaturesWrapper.java  |   5 +-
 .../sis/internal/netcdf/ucar/LogAdapter.java       |   9 +-
 .../apache/sis/storage/netcdf/MetadataReader.java  |   7 +-
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |  24 ++---
 .../sis/storage/netcdf/NetcdfStoreProvider.java    |   8 +-
 .../org/apache/sis/internal/netcdf/TestCase.java   |  11 +--
 .../internal/netcdf/impl/ChannelDecoderTest.java   |   3 +-
 .../storage/netcdf/NetcdfStoreProviderTest.java    |   9 +-
 .../apache/sis/internal/sql/feature/Analyzer.java  |   7 +-
 .../apache/sis/internal/sql/feature/Database.java  |   5 +-
 .../java/org/apache/sis/storage/sql/SQLStore.java  |  24 ++---
 .../sis/internal/storage/AbstractFeatureSet.java   |  22 ++---
 .../sis/internal/storage/AbstractGridResource.java |  22 +----
 .../sis/internal/storage/AbstractResource.java     |  89 +++++------------
 .../sis/internal/storage/AggregatedFeatureSet.java |  32 +-----
 .../internal/storage/ConcatenatedFeatureSet.java   |  23 +++--
 .../internal/storage/DocumentedStoreProvider.java  |   5 +-
 .../sis/internal/storage/JoinFeatureSet.java       |   9 +-
 .../sis/internal/storage/MemoryFeatureSet.java     |  11 +--
 .../apache/sis/internal/storage/URIDataStore.java  |  25 ++---
 .../apache/sis/internal/storage/folder/Store.java  |  24 ++---
 .../sis/internal/storage/io/ChannelFactory.java    |  33 +++----
 .../sis/internal/storage/query/FeatureSubset.java  |  13 +--
 .../sis/internal/storage/wkt/StoreFormat.java      |  14 ++-
 .../org/apache/sis/internal/storage/xml/Store.java |   3 +-
 .../java/org/apache/sis/storage/DataStore.java     |  82 ++++++++++++++--
 .../org/apache/sis/storage/event/StoreEvent.java   |  10 +-
 .../apache/sis/storage/event/StoreListeners.java   |  90 ++++++++++++++---
 .../internal/storage/AbstractGridResourceTest.java |   3 -
 .../java/org/apache/sis/storage/DataStoreMock.java |  40 ++++++--
 .../sis/storage/event/StoreListenersTest.java      | 109 +++++++++++++++++++++
 .../apache/sis/test/suite/StorageTestSuite.java    |   1 +
 .../internal/storage/xml/stream/StaxDataStore.java |   2 +-
 46 files changed, 483 insertions(+), 470 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java b/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java
index d7790cb..3f22af8 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/logging/WarningListeners.java
@@ -101,7 +101,6 @@ public class WarningListeners<S> implements Localized {
      *                but this is the source that the implementer wants to declare as public API.
      */
     public WarningListeners(final S source) {
-        ArgumentChecks.ensureNonNull("source", source);
         this.source = source;
     }
 
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/logging/EmptyWarningListeners.java b/core/sis-utility/src/test/java/org/apache/sis/util/logging/EmptyWarningListeners.java
deleted file mode 100644
index 7866034..0000000
--- a/core/sis-utility/src/test/java/org/apache/sis/util/logging/EmptyWarningListeners.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.util.logging;
-
-import java.util.Locale;
-import java.util.logging.Logger;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.resources.Errors;
-
-
-/**
- * A unmodifiable empty list of listeners. Calls to {@link #addWarningListener(WarningListener) addWarningListener(…)}
- * will throw {@link UnsupportedOperationException}. Since this listener list is empty, it does not need a source.
- *
- * <p>This class is used in some modules like {@code sis-netcdf}, when a JUnit test is testing some low-level
- * component where the real {@link WarningListeners} instance is not yet available.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
- *
- * @param <S>  if the listener list had a source, that would be type type of the source.
- *
- * @since 0.3
- * @module
- */
-public final strictfp class EmptyWarningListeners<S> extends WarningListeners<S> {
-    /**
-     * The locale to be returned by {@link #getLocale()}. Can be {@code null}.
-     */
-    private final Locale locale;
-
-    /**
-     * The logger to be returned by {@link #getLogger()}.
-     */
-    @SuppressWarnings("NonConstantLogger")
-    private final Logger logger;
-
-    /**
-     * Creates a new instance for the given locale and logger.
-     *
-     * @param locale  the locale to be returned by {@link #getLocale()}. Can be {@code null}.
-     * @param logger  the name of the logger to be returned by {@link #getLogger()}.
-     */
-    public EmptyWarningListeners(final Locale locale, final String logger) {
-        ArgumentChecks.ensureNonNull("logger", logger);
-        this.locale = locale;
-        this.logger = Logging.getLogger(logger);
-    }
-
-    /** @return the value given at construction time. */ @Override public Locale getLocale() {return locale;}
-    /** @return the value given at construction time. */ @Override public Logger getLogger() {return logger;}
-
-    /** Do not allow registration of warning listeners. */
-    @Override public void addWarningListener(WarningListener<? super S> listener) {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "WarningListeners"));
-    }
-}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/logging/WarningListenersTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/logging/WarningListenersTest.java
index c2ff86f..42b7318 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/logging/WarningListenersTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/logging/WarningListenersTest.java
@@ -33,6 +33,7 @@ import static org.junit.Assert.*;
  * @since   0.3
  * @module
  */
+@Deprecated
 public final strictfp class WarningListenersTest extends TestCase implements WarningListener<String> {
     /**
      * The object to be tested. Its source will be set to the string {@code "source"}.
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index cd7e610..333e6d2 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -51,14 +51,13 @@ import org.apache.sis.metadata.iso.content.DefaultCoverageDescription;
 import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
@@ -213,7 +212,7 @@ final class LandsatReader extends MetadataBuilder {
     /**
      * Where to send the warnings.
      */
-    private final WarningListeners<DataStore> listeners;
+    private final StoreListeners listeners;
 
     /**
      * Group in process of being parsed, or {@code null} if none.
@@ -294,7 +293,7 @@ final class LandsatReader extends MetadataBuilder {
      * @param  filename   an identifier of the file being read, or {@code null} if unknown.
      * @param  listeners  where to sent warnings that may occur during the parsing process.
      */
-    LandsatReader(final String filename, final WarningListeners<DataStore> listeners) {
+    LandsatReader(final String filename, final StoreListeners listeners) {
         this.filename  = filename;
         this.listeners = listeners;
         this.factories = new ReferencingFactoryContainer();
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
index c23a061..37b8672 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
@@ -33,6 +33,7 @@ import org.apache.sis.storage.UnsupportedStorageException;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.internal.storage.URIDataStore;
 import org.apache.sis.setup.OptionKey;
 
@@ -171,25 +172,16 @@ public class LandsatStore extends DataStore {
     }
 
     /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers a listener to notify when the specified kind of event occurs in this data store.
+     * The current implementation of this data store can emit only {@link WarningEvent}s;
+     * any listener specified for another kind of events will be ignored.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 
     /**
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
index c1c13f0..5e0567f 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
@@ -16,11 +16,11 @@
  */
 package org.apache.sis.storage.earthobservation;
 
-import java.util.Locale;
 import java.util.regex.Matcher;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import org.apache.sis.internal.storage.AbstractResource;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.acquisition.Context;
 import org.opengis.metadata.acquisition.OperationType;
@@ -34,8 +34,6 @@ import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.FactoryException;
 import org.opengis.test.dataset.ContentVerifier;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.util.logging.EmptyWarningListeners;
-import org.apache.sis.internal.system.Modules;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -96,8 +94,7 @@ public class LandsatReaderTest extends TestCase {
         try (BufferedReader in = new BufferedReader(new InputStreamReader(
                 LandsatReaderTest.class.getResourceAsStream("LandsatTest.txt"), "UTF-8")))
         {
-            final LandsatReader reader = new LandsatReader("LandsatTest.txt",
-                    new EmptyWarningListeners<>(Locale.ENGLISH, Modules.EARTH_OBSERVATION));
+            final LandsatReader reader = new LandsatReader("LandsatTest.txt", new AbstractResource(null));
             reader.read(in);
             actual = reader.getMetadata();
         }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 065dbdb..1689296 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -44,6 +44,8 @@ import org.apache.sis.storage.DataStoreClosedException;
 import org.apache.sis.storage.IllegalNameException;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.storage.io.IOUtilities;
 import org.apache.sis.internal.storage.MetadataBuilder;
@@ -144,6 +146,13 @@ public class GeoTiffStore extends DataStore implements Aggregate {
     }
 
     /**
+     * Opens access to listeners for {@link ImageFileDirectory}.
+     */
+    final StoreListeners listeners() {
+        return listeners;
+    }
+
+    /**
      * Returns the parameters used to open this GeoTIFF data store.
      * If non-null, the parameters are described by {@link GeoTiffStoreProvider#getOpenParameters()} and contains at
      * least a parameter named {@value org.apache.sis.storage.DataStoreProvider#LOCATION} with a {@link URI} value.
@@ -333,25 +342,16 @@ public class GeoTiffStore extends DataStore implements Aggregate {
     }
 
     /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers a listener to notify when the specified kind of event occurs in this data store.
+     * The current implementation of this data store can emit only {@link WarningEvent}s;
+     * any listener specified for another kind of events will be ignored.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 
     /**
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 94b50ae..4e07595 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -364,7 +364,7 @@ final class ImageFileDirectory extends AbstractGridResource {
      * @param index   the image index as a sequence number starting with 0 for the first image.
      */
     ImageFileDirectory(final Reader reader, final int index) {
-        super(reader.owner);
+        super(reader.owner.listeners());
         this.reader = reader;
         identifier = reader.nameFactory.createLocalName(reader.owner.identifier, String.valueOf(index + 1));
     }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 63c55ca..95e3046 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -35,13 +35,12 @@ import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.logging.PerformanceLevel;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
@@ -136,7 +135,7 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
     /**
      * Where to send the warnings.
      */
-    public final WarningListeners<DataStore> listeners;
+    public final StoreListeners listeners;
 
     /**
      * Sets to {@code true} for canceling a reading process.
@@ -150,7 +149,7 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
      * @param  geomlib    the library for geometric objects, or {@code null} for the default.
      * @param  listeners  where to send the warnings.
      */
-    protected Decoder(final GeometryLibrary geomlib, final WarningListeners<DataStore> listeners) {
+    protected Decoder(final GeometryLibrary geomlib, final StoreListeners listeners) {
         Objects.requireNonNull(listeners);
         this.geomlib      = geomlib;
         this.listeners    = listeners;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DiscreteSampling.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DiscreteSampling.java
index 1425b94..0e55aee 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DiscreteSampling.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DiscreteSampling.java
@@ -16,11 +16,10 @@
  */
 package org.apache.sis.internal.netcdf;
 
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.storage.AbstractFeatureSet;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.resources.Errors;
 
 
@@ -48,7 +47,7 @@ public abstract class DiscreteSampling extends AbstractFeatureSet {
      * @param  listeners  the set of registered warning listeners for the data store.
      * @throws IllegalArgumentException if the given library is non-null but not available.
      */
-    protected DiscreteSampling(final GeometryLibrary library, final WarningListeners<DataStore> listeners) {
+    protected DiscreteSampling(final GeometryLibrary library, final StoreListeners listeners) {
         super(listeners);
         factory = Geometries.implementation(library);
     }
@@ -59,6 +58,6 @@ public abstract class DiscreteSampling extends AbstractFeatureSet {
      * @return default error message to use in exceptions.
      */
     protected final String canNotReadFile() {
-        return Errors.getResources(getLocale()).getString(Errors.Keys.CanNotRead_1, getStoreName());
+        return Errors.getResources(getLocale()).getString(Errors.Keys.CanNotRead_1, getSourceName());
     }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
index b2a1ca3..9ac2622 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
@@ -21,9 +21,9 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Strings;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.resources.IndexedResourceBundle;
 
 
@@ -101,7 +101,7 @@ public abstract class NamedElement {
      * @param  key        one or {@link Resources.Keys} constants.
      * @param  arguments  values to be formatted in the {@link java.text.MessageFormat} pattern.
      */
-    static void warning(final WarningListeners<?> listeners, final Class<?> caller, final String method,
+    static void warning(final StoreListeners listeners, final Class<?> caller, final String method,
             final Exception exception, IndexedResourceBundle resources, final short key, final Object... arguments)
     {
         if (resources == null) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index d3551e9..32f876d 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -54,13 +54,12 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.StandardDateFormat;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.measure.Units;
 import org.apache.sis.math.Vector;
@@ -229,7 +228,7 @@ public final class ChannelDecoder extends Decoder {
      * @throws ArithmeticException if a variable is too large.
      */
     public ChannelDecoder(final ChannelDataInput input, final Charset encoding, final GeometryLibrary geomlib,
-            final WarningListeners<DataStore> listeners) throws IOException, DataStoreException
+            final StoreListeners listeners) throws IOException, DataStoreException
     {
         super(geomlib, listeners);
         this.input = input;
@@ -336,7 +335,7 @@ public final class ChannelDecoder extends Decoder {
     }
 
     /**
-     * Returns the localized error resource bundle for the locale given by {@link WarningListeners#getLocale()}.
+     * Returns the localized error resource bundle for the locale given by {@link StoreListeners#getLocale()}.
      *
      * @return the localized error resource bundle.
      */
@@ -345,7 +344,7 @@ public final class ChannelDecoder extends Decoder {
     }
 
     /**
-     * Returns the netCDF-specific resource bundle for the locale given by {@link WarningListeners#getLocale()}.
+     * Returns the netCDF-specific resource bundle for the locale given by {@link StoreListeners#getLocale()}.
      *
      * @return the localized error resource bundle.
      */
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
index 69e52a5..419af42 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
@@ -40,7 +40,6 @@ import ucar.nc2.ft.FeatureDatasetPoint;
 import ucar.nc2.ft.FeatureDatasetFactoryManager;
 import ucar.nc2.ft.FeatureCollection;
 import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.netcdf.Convention;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Variable;
@@ -48,8 +47,8 @@ import org.apache.sis.internal.netcdf.Node;
 import org.apache.sis.internal.netcdf.Grid;
 import org.apache.sis.internal.netcdf.DiscreteSampling;
 import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.event.StoreListeners;
 
 
 /**
@@ -108,7 +107,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
      * @param file       the netCDF file from which to read data.
      * @param listeners  where to send the warnings.
      */
-    public DecoderWrapper(final NetcdfFile file, final GeometryLibrary geomlib, final WarningListeners<DataStore> listeners) {
+    public DecoderWrapper(final NetcdfFile file, final GeometryLibrary geomlib, final StoreListeners listeners) {
         super(geomlib, listeners);
         this.file = file;
         groups = new Group[1];
@@ -124,7 +123,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
      * @throws IOException if an error occurred while opening the netCDF file.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    public DecoderWrapper(final String filename, final GeometryLibrary geomlib, final WarningListeners<DataStore> listeners)
+    public DecoderWrapper(final String filename, final GeometryLibrary geomlib, final StoreListeners listeners)
             throws IOException
     {
         super(geomlib, listeners);
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
index 4072b5c..8d8b3cb 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
@@ -17,10 +17,9 @@
 package org.apache.sis.internal.netcdf.ucar;
 
 import java.util.stream.Stream;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.internal.netcdf.DiscreteSampling;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 import ucar.nc2.ft.FeatureCollection;
 
 // Branch-dependent imports
@@ -49,7 +48,7 @@ final class FeaturesWrapper extends DiscreteSampling {
      * @param  listeners  the set of registered warning listeners for the data store.
      * @throws IllegalArgumentException if the given library is non-null but not available.
      */
-    FeaturesWrapper(final FeatureCollection features, final GeometryLibrary factory, final WarningListeners<DataStore> listeners) {
+    FeaturesWrapper(final FeatureCollection features, final GeometryLibrary factory, final StoreListeners listeners) {
         super(factory, listeners);
         this.features = features;
     }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java
index ef3ebd9..97c0d7b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/LogAdapter.java
@@ -16,15 +16,14 @@
  */
 package org.apache.sis.internal.netcdf.ucar;
 
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 
 
 /**
  * Forwards netCDF logging to the Apache SIS warning listeners.
  * NetCDF sends message to a user-specified {@link java.util.Formatter} with one message per line.
- * This class intercepts the characters and send them to the {@link WarningListeners} every time
+ * This class intercepts the characters and send them to the {@link StoreListeners} every time
  * that a complete line has been received.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -41,12 +40,12 @@ final class LogAdapter implements Appendable {
     /**
      * Where to sends the warning messages.
      */
-    private final WarningListeners<DataStore> listeners;
+    private final StoreListeners listeners;
 
     /**
      * Creates a new adapter which will send lines to the given listeners.
      */
-    LogAdapter(final WarningListeners<DataStore> listeners) {
+    LogAdapter(final StoreListeners listeners) {
         this.listeners = listeners;
     }
 
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 5532b33..8d1a4c7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -50,9 +50,8 @@ import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.util.logging.WarningListeners;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.metadata.iso.citation.*;
 import org.apache.sis.metadata.iso.identification.*;
@@ -203,12 +202,12 @@ final class MetadataReader extends MetadataBuilder {
 
     /**
      * Logs a warning using the localized error resource bundle for the locale given by
-     * {@link WarningListeners#getLocale()}.
+     * {@link StoreListeners#getLocale()}.
      *
      * @param  key  one of {@link Errors.Keys} values.
      */
     private void warning(final short key, final Object p1, final Object p2, final Exception e) {
-        final WarningListeners<DataStore> listeners = decoder.listeners;
+        final StoreListeners listeners = decoder.listeners;
         listeners.warning(Errors.getResources(listeners.getLocale()).getString(key, p1, p2), e);
     }
 
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
index 0e33f37..9eed48a 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
@@ -41,6 +41,7 @@ import org.apache.sis.setup.OptionKey;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Version;
 import ucar.nc2.constants.ACDD;
@@ -215,25 +216,16 @@ public class NetcdfStore extends DataStore implements Aggregate {
     }
 
     /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers a listener to notify when the specified kind of event occurs in this data store.
+     * The current implementation of this data store can emit only {@link WarningEvent}s;
+     * any listener specified for another kind of events will be ignored.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 
     /**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
index 70bd8d0..daee428 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
@@ -44,7 +44,7 @@ import org.apache.sis.storage.DataStoreProvider;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.ProbeResult;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.Version;
 
@@ -265,7 +265,7 @@ public class NetcdfStoreProvider extends DataStoreProvider {
      * @throws IOException if an error occurred while opening the netCDF file.
      * @throws DataStoreException if a logical error (other than I/O) occurred.
      */
-    static Decoder decoder(final WarningListeners<DataStore> listeners, final StorageConnector connector)
+    static Decoder decoder(final StoreListeners listeners, final StorageConnector connector)
             throws IOException, DataStoreException
     {
         final GeometryLibrary geomlib = connector.getOption(OptionKey.GEOMETRY_LIBRARY);
@@ -311,7 +311,7 @@ public class NetcdfStoreProvider extends DataStoreProvider {
      * @throws DataStoreException if a logical error (other than I/O) occurred.
      */
     private static Decoder createByReflection(final Object input, final boolean isUCAR,
-            final GeometryLibrary geomlib, final WarningListeners<DataStore> listeners)
+            final GeometryLibrary geomlib, final StoreListeners listeners)
             throws IOException, DataStoreException
     {
         ensureInitialized(true);
@@ -371,7 +371,7 @@ public class NetcdfStoreProvider extends DataStoreProvider {
                          */
                         final Class<? extends Decoder> wrapper =
                                 Class.forName("org.apache.sis.internal.netcdf.ucar.DecoderWrapper").asSubclass(Decoder.class);
-                        final Class<?>[] parameterTypes = new Class<?>[] {netcdfFileClass, GeometryLibrary.class, WarningListeners.class};
+                        final Class<?>[] parameterTypes = new Class<?>[] {netcdfFileClass, GeometryLibrary.class, StoreListeners.class};
                         createFromUCAR = wrapper.getConstructor(parameterTypes);
                         parameterTypes[0] = String.class;
                         createFromPath = wrapper.getConstructor(parameterTypes);
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
index 157922d..fc8365a 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
@@ -22,11 +22,9 @@ import java.util.EnumMap;
 import java.util.Iterator;
 import java.io.IOException;
 import java.lang.reflect.UndeclaredThrowableException;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.util.logging.EmptyWarningListeners;
+import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
-import org.apache.sis.internal.system.Modules;
 import org.apache.sis.setup.GeometryLibrary;
 import org.opengis.test.dataset.TestData;
 import ucar.nc2.dataset.NetcdfDataset;
@@ -49,11 +47,6 @@ import static org.junit.Assert.*;
  */
 public abstract strictfp class TestCase extends org.apache.sis.test.TestCase {
     /**
-     * A dummy list of listeners which can be given to the {@link Decoder} constructor.
-     */
-    protected static EmptyWarningListeners<DataStore> LISTENERS = new EmptyWarningListeners<>(null, Modules.NETCDF);
-
-    /**
      * The {@code searchPath} argument value to be given to the {@link Decoder#setSearchPath(String[])}
      * method when the decoder shall search only in global attributes.
      */
@@ -117,7 +110,7 @@ public abstract strictfp class TestCase extends org.apache.sis.test.TestCase {
      * @throws DataStoreException if a logical error occurred.
      */
     protected Decoder createDecoder(final TestData file) throws IOException, DataStoreException {
-        return new DecoderWrapper(new NetcdfDataset(createUCAR(file)), GeometryLibrary.JAVA2D, LISTENERS);
+        return new DecoderWrapper(new NetcdfDataset(createUCAR(file)), GeometryLibrary.JAVA2D, new AbstractResource(null));
     }
 
     /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
index 4e3a61c..a18cf5e 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
@@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.DecoderTest;
+import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.setup.GeometryLibrary;
@@ -70,6 +71,6 @@ public final strictfp class ChannelDecoderTest extends DecoderTest {
         final InputStream in = file.open();
         final ChannelDataInput input = new ChannelDataInput(file.name(),
                 Channels.newChannel(in), ByteBuffer.allocate(4096), false);
-        return new ChannelDecoder(input, null, GeometryLibrary.JAVA2D, LISTENERS);
+        return new ChannelDecoder(input, null, GeometryLibrary.JAVA2D, new AbstractResource(null));
     }
 }
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
index a798cb5..8acae77 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
@@ -23,6 +23,7 @@ import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
 import org.apache.sis.internal.netcdf.impl.ChannelDecoder;
 import org.apache.sis.internal.netcdf.impl.ChannelDecoderTest;
+import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.storage.ProbeResult;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
@@ -82,7 +83,7 @@ public final strictfp class NetcdfStoreProviderTest extends TestCase {
     }
 
     /**
-     * Tests {@link NetcdfStoreProvider#decoder(WarningListeners, StorageConnector)} for an input stream which
+     * Tests {@link NetcdfStoreProvider#decoder(StoreListeners, StorageConnector)} for an input stream which
      * shall be recognized as a classic netCDF file. The provider shall instantiate a {@link ChannelDecoder}.
      *
      * @throws IOException if an error occurred while opening the netCDF file.
@@ -91,13 +92,13 @@ public final strictfp class NetcdfStoreProviderTest extends TestCase {
     @Test
     public void testDecoderFromStream() throws IOException, DataStoreException {
         final StorageConnector c = new StorageConnector(TestData.NETCDF_2D_GEOGRAPHIC.open());
-        try (Decoder decoder = NetcdfStoreProvider.decoder(LISTENERS, c)) {
+        try (Decoder decoder = NetcdfStoreProvider.decoder(new AbstractResource(null), c)) {
             assertInstanceOf("decoder", ChannelDecoder.class, decoder);
         }
     }
 
     /**
-     * Tests {@link NetcdfStoreProvider#decoder(WarningListeners, StorageConnector)} for a UCAR
+     * Tests {@link NetcdfStoreProvider#decoder(StoreListeners, StorageConnector)} for a UCAR
      * {@link NetcdfFile} object. The provider shall instantiate a {@link DecoderWrapper}.
      *
      * @throws IOException if an error occurred while opening the netCDF file.
@@ -106,7 +107,7 @@ public final strictfp class NetcdfStoreProviderTest extends TestCase {
     @Test
     public void testDecoderFromUCAR() throws IOException, DataStoreException {
         final StorageConnector c = new StorageConnector(createUCAR(TestData.NETCDF_2D_GEOGRAPHIC));
-        try (Decoder decoder = NetcdfStoreProvider.decoder(LISTENERS, c)) {
+        try (Decoder decoder = NetcdfStoreProvider.decoder(new AbstractResource(null), c)) {
             assertInstanceOf("decoder", DecoderWrapper.class, decoder);
         }
     }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java
index a24d969..bb973e8 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java
@@ -38,10 +38,9 @@ import org.apache.sis.internal.metadata.sql.Dialect;
 import org.apache.sis.internal.metadata.sql.SQLUtilities;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.storage.sql.SQLStore;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.InternalDataStoreException;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.resources.ResourceInternationalString;
 
 
@@ -116,7 +115,7 @@ final class Analyzer {
     /**
      * Where to send warnings after we finished to collect them, or when reading the feature instances.
      */
-    final WarningListeners<DataStore> listeners;
+    final StoreListeners listeners;
 
     /**
      * The locale for warning messages.
@@ -142,7 +141,7 @@ final class Analyzer {
      * @param  listeners  Value of {@code SQLStore.listeners}.
      * @param  locale     Value of {@code SQLStore.getLocale()}.
      */
-    Analyzer(final DataSource source, final DatabaseMetaData metadata, final WarningListeners<DataStore> listeners,
+    Analyzer(final DataSource source, final DatabaseMetaData metadata, final StoreListeners listeners,
              final Locale locale) throws SQLException
     {
         this.source      = source;
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java
index 3f04da4..66ba910 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java
@@ -31,13 +31,12 @@ import org.apache.sis.internal.metadata.sql.Reflection;
 import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.storage.sql.SQLStore;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.FeatureNaming;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.IllegalNameException;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.Debug;
 
@@ -109,7 +108,7 @@ public final class Database {
      * @throws DataStoreException if a logical error occurred while analyzing the database structure.
      */
     public Database(final SQLStore store, final Connection connection, final DataSource source,
-            final GenericName[] tableNames, final WarningListeners<DataStore> listeners)
+            final GenericName[] tableNames, final StoreListeners listeners)
             throws SQLException, DataStoreException
     {
         final Analyzer analyzer = new Analyzer(source, connection.getMetaData(), listeners, store.getLocale());
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
index 0d83fd4..6ef63cf 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
@@ -34,6 +34,7 @@ import org.apache.sis.storage.IllegalNameException;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.internal.sql.feature.Database;
 import org.apache.sis.internal.sql.feature.Resources;
 import org.apache.sis.internal.storage.MetadataBuilder;
@@ -256,25 +257,16 @@ public class SQLStore extends DataStore implements Aggregate {
     }
 
     /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers a listener to notify when the specified kind of event occurs in this data store.
+     * The current implementation of this data store can emit only {@link WarningEvent}s;
+     * any listener specified for another kind of events will be ignored.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
index d3da090..e07f19b 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
@@ -18,11 +18,11 @@ package org.apache.sis.internal.storage;
 
 import java.util.Optional;
 import java.util.OptionalLong;
+import org.opengis.util.GenericName;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.util.logging.WarningListeners;
-import org.opengis.util.GenericName;
+import org.apache.sis.storage.event.StoreListeners;
 
 // Branch-dependent imports
 import org.opengis.feature.FeatureType;
@@ -40,7 +40,7 @@ import org.opengis.feature.FeatureType;
  *   <li>{@link #features(boolean parallel)} (mandatory)</li>
  * </ul>
  *
- * {@section Thread safety}
+ * <div class="section">Thread safety</div>
  * Default methods of this abstract class are thread-safe.
  * Synchronization, when needed, uses {@code this} lock.
  *
@@ -54,20 +54,10 @@ public abstract class AbstractFeatureSet extends AbstractResource implements Fea
     /**
      * Creates a new resource.
      *
-     * @param listeners  the set of registered warning listeners for the data store, or {@code null} if none.
-     */
-    protected AbstractFeatureSet(final WarningListeners<DataStore> listeners) {
-        super(listeners);
-    }
-
-    /**
-     * Creates a new feature set with the same warning listeners than the given resource,
-     * or with {@code null} listeners if they are unknown.
-     *
-     * @param resource  the resources from which to get the listeners, or {@code null} if none.
+     * @param  parent  listeners of the parent resource, or {@code null} if none.
      */
-    protected AbstractFeatureSet(final FeatureSet resource) {
-        super(resource);
+    protected AbstractFeatureSet(final StoreListeners parent) {
+        super(parent);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
index 944bc6f..0cd9832 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
@@ -19,18 +19,16 @@ package org.apache.sis.internal.storage;
 import java.util.Arrays;
 import java.util.Optional;
 import org.opengis.geometry.Envelope;
-import org.apache.sis.storage.DataStore;
+import org.opengis.metadata.spatial.DimensionNameType;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.math.MathFunctions;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
-import org.opengis.metadata.spatial.DimensionNameType;
 
 
 /**
@@ -45,20 +43,10 @@ public abstract class AbstractGridResource extends AbstractResource implements G
     /**
      * Creates a new resource.
      *
-     * @param listeners  the set of registered warning listeners for the data store, or {@code null} if none.
-     */
-    protected AbstractGridResource(final WarningListeners<DataStore> listeners) {
-        super(listeners);
-    }
-
-    /**
-     * Creates a new resource with the same warning listeners than the given resource,
-     * or {@code null} if the listeners are unknown.
-     *
-     * @param resource  the resources from which to get the listeners, or {@code null} if none.
+     * @param  parent  listeners of the parent resource, or {@code null} if none.
      */
-    protected AbstractGridResource(final Resource resource) {
-        super(resource);
+    protected AbstractGridResource(final StoreListeners parent) {
+        super(parent);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
index d3ca538..e4fbc2d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
@@ -16,18 +16,17 @@
  */
 package org.apache.sis.internal.storage;
 
-import java.util.Locale;
 import java.util.Optional;
+import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.util.Localized;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.event.WarningEvent;
 
 
 /**
@@ -40,7 +39,10 @@ import org.apache.sis.storage.event.StoreListener;
  *   <li>{@link #createMetadata(MetadataBuilder)} (optional)</li>
  * </ul>
  *
- * {@section Thread safety}
+ * This class extends {@link StoreListeners} for convenience reasons.
+ * This implementation details may change in any future SIS version.
+ *
+ * <div class="section">Thread safety</div>
  * Default methods of this abstract class are thread-safe.
  * Synchronization, when needed, uses {@code this} lock.
  *
@@ -49,7 +51,7 @@ import org.apache.sis.storage.event.StoreListener;
  * @since   0.8
  * @module
  */
-public abstract class AbstractResource implements Resource, Localized {
+public class AbstractResource extends StoreListeners implements Resource {
     /**
      * A description of this resource as an unmodifiable metadata, or {@code null} if not yet computed.
      * If non-null, this metadata shall contain at least the resource {@linkplain #getIdentifier() identifier}.
@@ -58,50 +60,22 @@ public abstract class AbstractResource implements Resource, Localized {
     private Metadata metadata;
 
     /**
-     * The set of registered warning listeners for the data store, or {@code null} if none.
-     */
-    private final WarningListeners<DataStore> listeners;
-
-    /**
      * Creates a new resource.
      *
-     * @param listeners  the set of registered warning listeners for the data store, or {@code null} if none.
+     * @param  parent  listeners of the parent resource, or {@code null} if none.
      */
-    protected AbstractResource(final WarningListeners<DataStore> listeners) {
-        this.listeners = listeners;
+    public AbstractResource(final StoreListeners parent) {
+        super(parent, null);
     }
 
     /**
-     * Creates a new resource with the same warning listeners than the given resource,
-     * or with {@code null} listeners if they are unknown.
-     *
-     * @param resource  the resources from which to get the listeners, or {@code null} if none.
-     */
-    protected AbstractResource(final Resource resource) {
-        listeners = (resource instanceof AbstractResource) ? ((AbstractResource) resource).listeners : null;
-    }
-
-    /**
-     * Returns the locale for error messages or warnings.
-     * Returns {@code null} if no locale is explicitly defined.
-     *
-     * @return the locale, or {@code null} if not explicitly defined.
+     * Returns the resource persistent identifier if available.
+     * The default implementation returns an empty value.
+     * Subclasses are strongly encouraged to override if they can provide a value.
      */
     @Override
-    public final Locale getLocale() {
-        return (listeners != null) ? listeners.getLocale() : null;
-    }
-
-    /**
-     * Returns the display name of the data store, or {@code null} if none.
-     * This is a convenience method for formatting error messages in subclasses.
-     *
-     * @return the data store display name, or {@code null}.
-     *
-     * @see DataStore#getDisplayName()
-     */
-    protected final String getStoreName() {
-        return (listeners != null) ? listeners.getSource().getDisplayName() : null;
+    public Optional<GenericName> getIdentifier() throws DataStoreException {
+        return Optional.empty();
     }
 
     /**
@@ -153,15 +127,6 @@ public abstract class AbstractResource implements Resource, Localized {
     }
 
     /**
-     * Invoked when a non-fatal exception occurred.
-     *
-     * @param  e  the non-fatal exception to report.
-     */
-    protected final void warning(final Exception e) {
-        listeners.warning(null, e);
-    }
-
-    /**
      * Clears any cache in this resource, forcing the data to be recomputed when needed again.
      * This method should be invoked if the data in underlying data store changed.
      */
@@ -170,24 +135,14 @@ public abstract class AbstractResource implements Resource, Localized {
     }
 
     /**
-     * Ignored in current implementation, on the assumption that most resources produce no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers only listeners for {@link WarningEvent}s on the assumption that most resources
+     * (at least the read-only ones) produce no change events.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, on the assumption that most resources produce no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
index 291bf24..5b48f15 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
@@ -20,16 +20,14 @@ import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Optional;
-import org.opengis.util.GenericName;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 
 // Branch-dependent imports
 import org.opengis.feature.FeatureType;
@@ -64,24 +62,10 @@ abstract class AggregatedFeatureSet extends AbstractFeatureSet {
     /**
      * Creates a new aggregated feature set.
      *
-     * @param  listeners  the set of registered warning listeners for the data store, or {@code null} if none.
+     * @param  parent  listeners of the parent resource, or {@code null} if none.
      */
-    protected AggregatedFeatureSet(final WarningListeners<DataStore> listeners) {
-        super(listeners);
-        /*
-         * TODO: we should add listeners on source feature sets. By doing this,
-         *       we could be notified of changes and invoke clearCache().
-         */
-    }
-
-    /**
-     * Creates a new feature set with the same warning listeners than the given resource,
-     * or with {@code null} listeners if they are unknown.
-     *
-     * @param resource  the resources from which to get the listeners, or {@code null} if none.
-     */
-    protected AggregatedFeatureSet(final FeatureSet resource) {
-        super(resource);
+    protected AggregatedFeatureSet(final StoreListeners parent) {
+        super(parent);
     }
 
     /**
@@ -93,14 +77,6 @@ abstract class AggregatedFeatureSet extends AbstractFeatureSet {
     abstract Collection<FeatureSet> dependencies();
 
     /**
-     * Returns an empty value since this resource is a computation result.
-     */
-    @Override
-    public Optional<GenericName> getIdentifier() {
-        return Optional.empty();
-    }
-
-    /**
      * Adds the envelopes of the aggregated feature sets in the given list. If some of the feature sets
      * are themselves aggregated feature sets, then this method traverses them recursively. We compute
      * the union of all envelopes at once after we got all envelopes.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
index e0de321..3e0b5a7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
@@ -23,15 +23,14 @@ import java.util.OptionalLong;
 import java.util.stream.Stream;
 import org.apache.sis.feature.Features;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.Query;
 
 // Branch-dependent imports
@@ -68,10 +67,10 @@ public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
     private final FeatureType commonType;
 
     /**
-     * Creates a new concatenated feature set with the same listeners and types than the given feature set,
+     * Creates a new concatenated feature set with the same types than the given feature set,
      * but different sources.
      */
-    private ConcatenatedFeatureSet(final ConcatenatedFeatureSet original, final FeatureSet[] sources) {
+    private ConcatenatedFeatureSet(final FeatureSet[] sources, final ConcatenatedFeatureSet original) {
         super(original);
         this.sources = UnmodifiableArrayList.wrap(sources);
         commonType = original.commonType;
@@ -83,13 +82,13 @@ public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
      * this verification must be done by the caller. This constructor retains the given {@code sources} array
      * by direct reference; clone, if desired, shall be done by the caller.
      *
-     * @param  listeners  the set of registered warning listeners for the data store, or {@code null} if none.
-     * @param  sources    the sequence of feature sets to expose in a single set.
-     *                    Must neither be null, empty nor contain a single element only.
+     * @param  parent   listeners of the parent resource, or {@code null} if none.
+     * @param  sources  the sequence of feature sets to expose in a single set.
+     *                  Must neither be null, empty nor contain a single element only.
      * @throws DataStoreException if given feature sets does not share any common type.
      */
-    protected ConcatenatedFeatureSet(final WarningListeners<DataStore> listeners, final FeatureSet[] sources) throws DataStoreException {
-        super(listeners);
+    protected ConcatenatedFeatureSet(final StoreListeners parent, final FeatureSet[] sources) throws DataStoreException {
+        super(parent);
         for (int i=0; i<sources.length; i++) {
             ArgumentChecks.ensureNonNullElement("sources", i, sources[i]);
         }
@@ -120,7 +119,7 @@ public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
             ArgumentChecks.ensureNonNullElement("sources", 0, fs);
             return fs;
         } else {
-            return new ConcatenatedFeatureSet((WarningListeners<DataStore>) null, sources.clone());
+            return new ConcatenatedFeatureSet(null, sources.clone());
         }
     }
 
@@ -143,7 +142,7 @@ public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
                 return fs;
             }
             default: {
-                return new ConcatenatedFeatureSet((WarningListeners<DataStore>) null, sources.toArray(new FeatureSet[size]));
+                return new ConcatenatedFeatureSet(null, sources.toArray(new FeatureSet[size]));
             }
         }
     }
@@ -236,6 +235,6 @@ public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
             subsets[i] = source.subset(query);
             modified |= subsets[i] != source;
         }
-        return modified ? new ConcatenatedFeatureSet(this, subsets) : this;
+        return modified ? new ConcatenatedFeatureSet(subsets, this) : this;
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java
index ab8b8b2..6170f40 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java
@@ -21,9 +21,8 @@ import java.util.logging.LogRecord;
 import org.opengis.metadata.distribution.Format;
 import org.apache.sis.metadata.sql.MetadataSource;
 import org.apache.sis.metadata.sql.MetadataStoreException;
-import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.system.Modules;
 
 
@@ -89,7 +88,7 @@ public abstract class DocumentedStoreProvider extends URIDataStore.Provider {
      * @param  listeners  where to report the warning in case of error, or {@code null} if none.
      * @return a description of the data format.
      */
-    public final Format getFormat(final WarningListeners<DataStore> listeners) {
+    public final Format getFormat(final StoreListeners listeners) {
         /*
          * Note: this method does not cache the format because such caching is already done by MetadataSource.
          */
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
index bc501b5..30ffcec 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
@@ -30,13 +30,12 @@ import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.feature.DefaultAssociationRole;
 import org.apache.sis.internal.feature.AttributeConvention;
 import org.apache.sis.internal.storage.query.SimpleQuery;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.collection.Containers;
-import org.apache.sis.util.logging.WarningListeners;
 
 // Branch-dependent imports
 import org.opengis.feature.Feature;
@@ -198,7 +197,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
      *   <li>{@code "identifierSuffix"} — string to insert at the end of join identifiers (optional).</li>
      * </ul>
      *
-     * @param  listeners    the set of registered warning listeners for the data store, or {@code null} if none.
+     * @param  parent       listeners of the parent resource, or {@code null} if none.
      * @param  left         the first source of features. This is often (but not necessarily) the largest set.
      * @param  leftAlias    name of the associations to the {@code left} features, or {@code null} for a default name.
      * @param  right        the second source of features. Should be the set in which iterations are cheapest.
@@ -208,14 +207,14 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
      * @param  featureInfo  information about the {@link FeatureType} of this
      * @throws DataStoreException if an error occurred while creating the feature set.
      */
-    public JoinFeatureSet(final WarningListeners<DataStore> listeners,
+    public JoinFeatureSet(final StoreListeners parent,
                           final FeatureSet left,  String leftAlias,
                           final FeatureSet right, String rightAlias,
                           final Type joinType, final PropertyIsEqualTo condition,
                           Map<String,?> featureInfo)
             throws DataStoreException
     {
-        super(listeners);
+        super(parent);
         final FeatureType leftType  = left.getType();
         final FeatureType rightType = right.getType();
         final GenericName leftName  = leftType.getName();
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
index 065d2ff..061bbdf 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
@@ -19,9 +19,8 @@ package org.apache.sis.internal.storage;
 import java.util.Collection;
 import java.util.OptionalLong;
 import java.util.stream.Stream;
-import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.logging.WarningListeners;
 
 // Branch-dependent imports
 import org.opengis.feature.Feature;
@@ -55,14 +54,12 @@ public class MemoryFeatureSet extends AbstractFeatureSet {
      * <code>{@linkplain Feature#getType()} == type</code> for all elements in the given collection
      * (this is not verified).
      *
-     * @param listeners  the set of registered warning listeners for the data store, or {@code null} if none.
+     * @param parent     listeners of the parent resource, or {@code null} if none.
      * @param type       the type of all features in the given collection.
      * @param features   collection of stored features. This collection will not be copied.
      */
-    public MemoryFeatureSet(final WarningListeners<DataStore> listeners,
-                            final FeatureType type, final Collection<Feature> features)
-    {
-        super(listeners);
+    public MemoryFeatureSet(final StoreListeners parent, final FeatureType type, final Collection<Feature> features) {
+        super(parent);
         ArgumentChecks.ensureNonNull("type",     type);
         ArgumentChecks.ensureNonNull("features", features);
         this.type     = type;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
index 25f179e..7db0fbb 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java
@@ -33,6 +33,7 @@ import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.IllegalOpenParameterException;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.internal.storage.io.IOUtilities;
 
 
@@ -268,26 +269,14 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R
     }
 
     /**
-     * Ignored in current implementation, on the assumption that most data stores
-     * (at least the read-only ones) produce no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers only listeners for {@link WarningEvent}s on the assumption that most data stores
+     * (at least the read-only ones) produce no change events.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, on the assumption that most data stores
-     * (at least the read-only ones) produce no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index 6b46c89..6b42068 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -57,6 +57,7 @@ import org.apache.sis.internal.storage.StoreResource;
 import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.WarningEvent;
 
 
 /**
@@ -403,25 +404,16 @@ class Store extends DataStore implements StoreResource, Aggregate, DirectoryStre
     }
 
     /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
+     * Registers a listener to notify when the specified kind of event occurs in this data store.
+     * The current implementation of this data store can emit only {@link WarningEvent}s;
+     * any listener specified for another kind of events will be ignored.
      */
     @Override
     public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
-    }
-
-    /**
-     * Ignored in current implementation, since this resource produces no events.
-     *
-     * @param  <T>        {@inheritDoc}
-     * @param  listener   {@inheritDoc}
-     * @param  eventType  {@inheritDoc}
-     */
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        // If an argument is null, we let the parent class throws (indirectly) NullArgumentException.
+        if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) {
+            super.addListener(listener, eventType);
+        }
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
index 41c05e4..0f4dc55 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
@@ -45,12 +45,11 @@ import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.storage.Resources;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.ForwardOnlyStorageException;
+import org.apache.sis.storage.event.StoreListeners;
 
 
 /**
@@ -231,10 +230,10 @@ public abstract class ChannelFactory {
         if (storage instanceof URL) {
             final URL file = (URL) storage;
             return new ChannelFactory() {
-                @Override public ReadableByteChannel readable(String filename, WarningListeners<DataStore> listeners) throws IOException {
+                @Override public ReadableByteChannel readable(String filename, StoreListeners listeners) throws IOException {
                     return Channels.newChannel(file.openStream());
                 }
-                @Override public WritableByteChannel writable(String filename, WarningListeners<DataStore> listeners) throws IOException {
+                @Override public WritableByteChannel writable(String filename, StoreListeners listeners) throws IOException {
                     return Channels.newChannel(file.openConnection().getOutputStream());
                 }
             };
@@ -252,10 +251,10 @@ public abstract class ChannelFactory {
             final Path path = (Path) storage;
             if (!Files.isDirectory(path)) {
                 return new ChannelFactory() {
-                    @Override public ReadableByteChannel readable(String filename, WarningListeners<DataStore> listeners) throws IOException {
+                    @Override public ReadableByteChannel readable(String filename, StoreListeners listeners) throws IOException {
                         return Files.newByteChannel(path, optionSet);
                     }
-                    @Override public WritableByteChannel writable(String filename, WarningListeners<DataStore> listeners) throws IOException {
+                    @Override public WritableByteChannel writable(String filename, StoreListeners listeners) throws IOException {
                         return Files.newByteChannel(path, optionSet);
                     }
                 };
@@ -297,7 +296,7 @@ public abstract class ChannelFactory {
      * @throws DataStoreException if the channel is read-once.
      * @throws IOException if the input stream or its underlying byte channel can not be created.
      */
-    public InputStream inputStream(String filename, WarningListeners<DataStore> listeners)
+    public InputStream inputStream(String filename, StoreListeners listeners)
             throws DataStoreException, IOException
     {
         return Channels.newInputStream(readable(filename, listeners));
@@ -313,7 +312,7 @@ public abstract class ChannelFactory {
      * @throws DataStoreException if the channel is write-once.
      * @throws IOException if the output stream or its underlying byte channel can not be created.
      */
-    public OutputStream outputStream(String filename, WarningListeners<DataStore> listeners)
+    public OutputStream outputStream(String filename, StoreListeners listeners)
             throws DataStoreException, IOException
     {
         return Channels.newOutputStream(writable(filename, listeners));
@@ -330,7 +329,7 @@ public abstract class ChannelFactory {
      * @throws DataStoreException if the channel is read-once.
      * @throws IOException if an error occurred while opening the channel.
      */
-    public abstract ReadableByteChannel readable(String filename, WarningListeners<DataStore> listeners)
+    public abstract ReadableByteChannel readable(String filename, StoreListeners listeners)
             throws DataStoreException, IOException;
 
     /**
@@ -344,7 +343,7 @@ public abstract class ChannelFactory {
      * @throws DataStoreException if the channel is write-once.
      * @throws IOException if an error occurred while opening the channel.
      */
-    public abstract WritableByteChannel writable(String filename, WarningListeners<DataStore> listeners)
+    public abstract WritableByteChannel writable(String filename, StoreListeners listeners)
             throws DataStoreException, IOException;
 
     /**
@@ -385,7 +384,7 @@ public abstract class ChannelFactory {
          * throws an exception on all subsequent invocations.
          */
         @Override
-        public ReadableByteChannel readable(final String filename, final WarningListeners<DataStore> listeners)
+        public ReadableByteChannel readable(final String filename, final StoreListeners listeners)
                 throws DataStoreException, IOException
         {
             final Channel in = channel;
@@ -407,7 +406,7 @@ public abstract class ChannelFactory {
          * throws an exception on all subsequent invocations.
          */
         @Override
-        public WritableByteChannel writable(final String filename, final WarningListeners<DataStore> listeners)
+        public WritableByteChannel writable(final String filename, final StoreListeners listeners)
                 throws DataStoreException, IOException
         {
             final Channel out = channel;
@@ -459,7 +458,7 @@ public abstract class ChannelFactory {
          * {@link File} to a {@link Path}. On all subsequent invocations, the file is opened silently.</p>
          */
         @Override
-        public FileInputStream inputStream(final String filename, final WarningListeners<DataStore> listeners)
+        public FileInputStream inputStream(final String filename, final StoreListeners listeners)
                 throws IOException
         {
             final FileInputStream in;
@@ -489,7 +488,7 @@ public abstract class ChannelFactory {
          * {@link File} to a {@link Path}. On all subsequent invocations, the file is opened silently.</p>
          */
         @Override
-        public FileOutputStream outputStream(final String filename, final WarningListeners<DataStore> listeners)
+        public FileOutputStream outputStream(final String filename, final StoreListeners listeners)
                 throws IOException
         {
             final FileOutputStream out;
@@ -511,7 +510,7 @@ public abstract class ChannelFactory {
          * Since the exception was nevertheless unexpected, log its stack trace in order to allow the developer
          * to check if there is something wrong.
          */
-        private void warning(final String method, final WarningListeners<DataStore> listeners) {
+        private void warning(final String method, final StoreListeners listeners) {
             if (cause != null) {
                 final LogRecord record = new LogRecord(Level.WARNING, cause.toString());
                 record.setLoggerName(Modules.STORAGE);
@@ -531,7 +530,7 @@ public abstract class ChannelFactory {
          * Opens a new channel for the file given at construction time.
          */
         @Override
-        public ReadableByteChannel readable(String filename, WarningListeners<DataStore> listeners) throws IOException {
+        public ReadableByteChannel readable(String filename, StoreListeners listeners) throws IOException {
             return inputStream(filename, listeners).getChannel();
         }
 
@@ -539,7 +538,7 @@ public abstract class ChannelFactory {
          * Opens a new channel for the file given at construction time.
          */
         @Override
-        public WritableByteChannel writable(String filename, WarningListeners<DataStore> listeners) throws IOException {
+        public WritableByteChannel writable(String filename, StoreListeners listeners) throws IOException {
             return outputStream(filename, listeners).getChannel();
         }
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
index 828feb5..2a2b887 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
@@ -17,9 +17,7 @@
 package org.apache.sis.internal.storage.query;
 
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Stream;
-import org.opengis.util.GenericName;
 import org.apache.sis.internal.feature.FeatureUtilities;
 import org.apache.sis.internal.storage.AbstractFeatureSet;
 import org.apache.sis.internal.storage.Resources;
@@ -27,6 +25,7 @@ import org.apache.sis.filter.InvalidExpressionException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.event.StoreListeners;
 
 // Branch-dependent imports
 import org.opengis.feature.Feature;
@@ -69,20 +68,12 @@ final class FeatureSubset extends AbstractFeatureSet {
      * Creates a new set of features by filtering the given set using the given query.
      */
     FeatureSubset(final FeatureSet source, final SimpleQuery query) {
-        super(source);
+        super(source instanceof StoreListeners ? (StoreListeners) source : null);
         this.source = source;
         this.query = query;
     }
 
     /**
-     * Returns an empty value since this resource is a computation result.
-     */
-    @Override
-    public Optional<GenericName> getIdentifier() {
-        return Optional.empty();
-    }
-
-    /**
      * Returns a description of properties that are common to all features in this dataset.
      */
     @Override
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
index 8d042c2..d7342f0 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
@@ -25,7 +25,6 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.io.wkt.Warnings;
 import org.apache.sis.referencing.CRS;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.internal.feature.Geometries;
@@ -33,7 +32,7 @@ import org.apache.sis.internal.feature.GeometryWrapper;
 import org.apache.sis.internal.referencing.DefinitionVerifier;
 import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.internal.system.Loggers;
-import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.ArraysExt;
 
 
@@ -57,7 +56,7 @@ public final class StoreFormat extends WKTFormat {
     /**
      * Where to send warnings.
      */
-    private final WarningListeners<DataStore> listeners;
+    private final StoreListeners listeners;
 
     /**
      * Creates a new WKT parser and encoder.
@@ -65,7 +64,7 @@ public final class StoreFormat extends WKTFormat {
      * @param  library    the geometry library, or {@code null} for the default.
      * @param  listeners  where to send warnings.
      */
-    public StoreFormat(final GeometryLibrary library, final WarningListeners<DataStore> listeners) {
+    public StoreFormat(final GeometryLibrary library, final StoreListeners listeners) {
         super(null, null);
         this.library   = library;
         this.listeners = listeners;
@@ -152,10 +151,9 @@ public final class StoreFormat extends WKTFormat {
      * Reports a warning for a WKT that can not be read. This method should be invoked only when the CRS
      * can not be created at all; it should not be invoked if the CRS has been created with some warnings.
      */
-    final void log(final Exception e) {
-        final DataStore store = listeners.getSource();
-        listeners.warning(Resources.forLocale(store.getLocale())
-                .getString(Resources.Keys.CanNotReadCRS_WKT_1, store.getDisplayName()), e);
+    private void log(final Exception e) {
+        listeners.warning(Resources.forLocale(listeners.getLocale())
+                .getString(Resources.Keys.CanNotReadCRS_WKT_1, listeners.getSourceName()), e);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
index e6a1509..4000afa 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
@@ -33,6 +33,7 @@ import org.apache.sis.xml.XML;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.UnsupportedStorageException;
+import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.util.resources.Errors;
@@ -121,7 +122,7 @@ final class Store extends URIDataStore {
      * Returns the properties to give to the (un)marshaller.
      */
     private Map<String,?> properties() {
-        if (listeners.hasListeners()) {
+        if (listeners.hasListeners(WarningEvent.class)) {
             return Collections.singletonMap(XML.WARNING_LISTENER, new WarningListener<Object>() {
                 /** Returns the type of objects that emit warnings of interest for this listener. */
                 @Override public Class<Object> getSourceClass() {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 3b4577e..194a435 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@ -31,11 +31,13 @@ import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.WarningListener;
-import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.referencing.NamedIdentifier;
+import org.apache.sis.storage.event.StoreEvent;
+import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.StoreListeners;
 
 
 /**
@@ -100,9 +102,9 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
     private Locale locale;
 
     /**
-     * The set of registered {@link WarningListener}s for this data store.
+     * The set of registered {@link StoreListener}s for this data store.
      */
-    protected final WarningListeners<DataStore> listeners;
+    protected final StoreListeners listeners;
 
     /**
      * Creates a new instance with no provider and initially no listener.
@@ -111,7 +113,7 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
         provider  = null;
         name      = null;
         locale    = Locale.getDefault(Locale.Category.DISPLAY);
-        listeners = new WarningListeners<>(this);
+        listeners = new StoreListeners(null, this);
     }
 
     /**
@@ -130,7 +132,7 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
         this.provider  = provider;
         this.name      = connector.getStorageName();
         this.locale    = Locale.getDefault(Locale.Category.DISPLAY);
-        this.listeners = new WarningListeners<>(this);
+        this.listeners = new StoreListeners(null, this);
         /*
          * Above locale is NOT OptionKey.LOCALE because we are not talking about the same locale.
          * The one in this DataStore is for warning and exception messages, not for parsing data.
@@ -140,8 +142,8 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
     /**
      * Creates a new instance as a child of another data store instance.
      * The new instance inherits the parent {@linkplain #getProvider() provider}.
-     * The parent and the child share the same listeners: adding or removing a listener to a parent
-     * adds or removes the same listeners to all children, and conversely.
+     * Events created by this {@code DataStore} are forwarded to listeners registered
+     * into the parent data store too.
      *
      * @param  parent     the parent data store, or {@code null} if none.
      * @param  connector  information about the storage (URL, stream, reader instance, <i>etc</i>).
@@ -151,15 +153,17 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
      */
     protected DataStore(final DataStore parent, final StorageConnector connector) throws DataStoreException {
         ArgumentChecks.ensureNonNull("connector", connector);
+        final StoreListeners forwardTo;
         if (parent != null) {
+            forwardTo = parent.listeners;
             provider  = parent.provider;
             locale    = parent.locale;
-            listeners = parent.listeners;
         } else {
+            forwardTo = null;
             provider  = null;
             locale    = Locale.getDefault(Locale.Category.DISPLAY);
-            listeners = new WarningListeners<>(this);
         }
+        listeners = new StoreListeners(forwardTo, this);
         name = connector.getStorageName();
     }
 
@@ -422,6 +426,66 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
     }
 
     /**
+     * Registers a listener to notify when the specified kind of event occurs in this data store or in a resource.
+     * The data store will call the {@link StoreListener#eventOccured(StoreEvent)} method when new events matching
+     * the {@code eventType} occur. An event may be a change in data store content or structure, or a warning that
+     * occurred during a read or write operation.
+     *
+     * <p>Registering a listener for a given {@code eventType} also register the listener for all event sub-types.
+     * The same listener can be registered many times, but its {@link StoreListener#eventOccured(StoreEvent)}
+     * method will be invoked only once per event. This filtering applies even if the listener is registered
+     * on individual resources of this data store.</p>
+     *
+     * <p>If this data store may produce events of the given type, then the given listener is kept by strong reference;
+     * it will not be garbage collected unless {@linkplain #removeListener(StoreListener, Class) explicitly removed}
+     * or unless this {@code DataStore} is itself garbage collected. However if the given type of events can never
+     * happen with this data store, then this method is not required to keep a reference to the given listener.</p>
+     *
+     * <div class="section">Warning events</div>
+     * If {@code eventType} is assignable from <code>{@linkplain org.apache.sis.storage.event.WarningEvent}.class</code>,
+     * then registering that listener turns off logging of warning messages for this data store.
+     * This side-effect is applied on the assumption that the registered listener will handle
+     * warnings in its own way, for example by showing warnings in a widget.
+     *
+     * @param  <T>        compile-time value of the {@code eventType} argument.
+     * @param  listener   listener to notify about events.
+     * @param  eventType  type of {@link StoreEvent} to listen (can not be {@code null}).
+     *
+     * @since 1.0
+     */
+    @Override
+    public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
+        listeners.addListener(listener, eventType);
+    }
+
+    /**
+     * Unregisters a listener previously added to this data store for the given type of events.
+     * The {@code eventType} must be the exact same class than the one given to the {@code addListener(…)} method;
+     * this method does not remove listeners registered for subclasses and does not remove listeners registered in
+     * children resources.
+     *
+     * <p>If the same listener has been registered many times for the same even type, then this method removes only
+     * the most recent registration. In other words if {@code addListener(ls, type)} has been invoked twice, then
+     * {@code removeListener(ls, type)} needs to be invoked twice in order to remove all instances of that listener.
+     * If the given listener is not found, then this method does nothing (no exception is thrown).</p>
+     *
+     * <div class="section">Warning events</div>
+     * If {@code eventType} is <code>{@linkplain org.apache.sis.storage.event.WarningEvent}.class</code>
+     * and if, after this method invocation, there is no remaining listener for warning events,
+     * then this {@code DataStore} will send future warnings to the loggers.
+     *
+     * @param  <T>        compile-time value of the {@code eventType} argument.
+     * @param  listener   listener to stop notifying about events.
+     * @param  eventType  type of {@link StoreEvent} which were listened (can not be {@code null}).
+     *
+     * @since 1.0
+     */
+    @Override
+    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+        listeners.removeListener(listener, eventType);
+    }
+
+    /**
      * Adds a listener to be notified when a warning occurred while reading from or writing to the storage.
      * When a warning occurs, there is a choice:
      *
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
index 3360ef9..7b6603f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
@@ -76,15 +76,9 @@ public abstract class StoreEvent extends EventObject implements Localized {
      */
     @Override
     public Locale getLocale() {
-        return getLocale(source);
-    }
-
-    /**
-     * {@link #getLocale()} implementation shared with {@link StoreListeners#getLocale()}.
-     */
-    static Locale getLocale(final Object source) {
         if (source instanceof Localized) {
-            return ((Localized) source).getLocale();
+            final Locale locale = ((Localized) source).getLocale();
+            if (locale != null) return locale;
         }
         if (source instanceof StoreResource) {
             final DataStore ds = ((StoreResource) source).getOriginator();
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
index 65e274b..01521cb 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
@@ -23,6 +23,7 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
 import java.lang.reflect.Method;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.Exceptions;
@@ -75,7 +76,7 @@ import org.apache.sis.storage.Resource;
  * @since   1.0
  * @module
  */
-public class StoreListeners extends org.apache.sis.util.logging.WarningListeners<Resource> implements Localized {
+public class StoreListeners extends org.apache.sis.util.logging.WarningListeners implements Localized {
     /**
      * Parent manager to notify in addition to this manager.
      */
@@ -208,9 +209,18 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
      * @param parent  the manager to notify in addition to this manager, or {@code null} if none.
      * @param source  the source of events. Can not be null.
      */
-    public StoreListeners(final StoreListeners parent, final Resource source) {
+    public StoreListeners(final StoreListeners parent, Resource source) {
         super(source);
-        ArgumentChecks.ensureNonNull("source", source);
+        /*
+         * Undocumented feature for allowing subclass to specify `this` as the source resource.
+         * This is used as a convenience by AbstractResource internal class. We need this hack
+         * because subclasses can not reference `this` before super-class constructor completed.
+         */
+        if (source == null && this instanceof Resource) {
+            source = (Resource) this;
+        } else {
+            ArgumentChecks.ensureNonNull("source", source);
+        }
         this.source = source;
         this.parent = parent;
     }
@@ -244,6 +254,35 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
     }
 
     /**
+     * Returns a short name or label for the source. It may be the name of the file opened by a data store.
+     * The returned name can be useful in warning messages for identifying the problematic source.
+     *
+     * <p>The default implementation {@linkplain DataStore#getDisplayName() fetches that name from the data store},
+     * or returns an arbitrary name if it can get it otherwise.</p>
+     *
+     * @return a short name of label for the source (never {@code null}).
+     *
+     * @see DataStore#getDisplayName()
+     */
+    public String getSourceName() {
+        final DataStore ds = getDataStore(this);
+        if (ds != null) {
+            String name = ds.getDisplayName();
+            if (name != null) {
+                return name;
+            }
+            final DataStoreProvider provider = ds.getProvider();
+            if (provider != null) {
+                name = provider.getShortName();
+                if (name != null) {
+                    return name;
+                }
+            }
+        }
+        return Classes.getShortClassName(source);
+    }
+
+    /**
      * Returns the locale used by this manager, or {@code null} if unspecified.
      * That locale is typically inherited from the {@link DataStore} locale
      * and can be used for formatting messages.
@@ -255,7 +294,16 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
      */
     @Override
     public Locale getLocale() {
-        return StoreEvent.getLocale(source);
+        StoreListeners m = this;
+        do {
+            final Resource src = m.source;
+            if (src != this && src != m && src instanceof Localized) {
+                final Locale locale = ((Localized) src).getLocale();
+                if (locale != null) return locale;
+            }
+            m = m.parent;
+        } while (m != null);
+        return null;
     }
 
     /**
@@ -507,6 +555,7 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
         }
         if (ce == null) {
             ce = new ForType<>(eventType, listeners);
+            listeners = ce;
         }
         ce.add(listener);
     }
@@ -546,16 +595,22 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
     }
 
     /**
-     * Returns {@code true} if this object contains at least one listener.
+     * Returns {@code true} if this object or its parent contains at least one listener for the given type of event.
      *
-     * @return {@code true} if this object contains at least one listener, {@code false} otherwise.
+     * @param  eventType  the type of event for which to check listener presence.
+     * @return {@code true} if this object contains at least one listener for given event type, {@code false} otherwise.
      */
-    @Override
-    public boolean hasListeners() {
+    public boolean hasListeners(final Class<? extends StoreEvent> eventType) {
+        ArgumentChecks.ensureNonNull("eventType", eventType);
         StoreListeners m = this;
         do {
-            if (listeners != null && listeners.hasListeners()) {
-                return true;
+            for (ForType<?> e = m.listeners; e != null; e = e.next) {
+                if (eventType.isAssignableFrom(e.type)) {
+                    if (e.hasListeners()) {
+                        return true;
+                    }
+                    break;
+                }
             }
             m = m.parent;
         } while (m != null);
@@ -563,11 +618,22 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
     }
 
     /**
+     * Returns {@code true} if this object contains at least one listener.
+     *
+     * @return {@code true} if this object contains at least one listener, {@code false} otherwise.
+     */
+    @Override
+    @Deprecated
+    public boolean hasListeners() {
+        return hasListeners(StoreEvent.class);
+    }
+
+    /**
      * @deprecated Replaced by {@code addListener(listener, WarningEvent.class)}.
      */
     @Override
     @Deprecated
-    public void addWarningListener(final WarningListener<? super Resource> listener) {
+    public void addWarningListener(final WarningListener listener) {
         addListener(new Legacy(listener), WarningEvent.class);
     }
 
@@ -576,7 +642,7 @@ public class StoreListeners extends org.apache.sis.util.logging.WarningListeners
      */
     @Override
     @Deprecated
-    public void removeWarningListener(final WarningListener<? super Resource> listener) {
+    public void removeWarningListener(final WarningListener listener) {
         for (ForType<?> e = listeners; e != null; e = e.next) {
             if (e.type.equals(WarningEvent.class)) {
                 StoreListener<?>[] list = e.listeners;
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/AbstractGridResourceTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/AbstractGridResourceTest.java
index f4b9f1b..37bfeea 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/AbstractGridResourceTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/AbstractGridResourceTest.java
@@ -17,8 +17,6 @@
 package org.apache.sis.internal.storage;
 
 import java.util.List;
-import java.util.Optional;
-import org.opengis.util.GenericName;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -42,7 +40,6 @@ public final strictfp class AbstractGridResourceTest  extends TestCase {
      * A resource performing no operation.
      */
     private final AbstractGridResource resource = new AbstractGridResource((AbstractGridResource) null) {
-        @Override public Optional<GenericName> getIdentifier()       {return Optional.empty();}
         @Override public GridGeometry          getGridGeometry()     {throw new UnsupportedOperationException();}
         @Override public List<SampleDimension> getSampleDimensions() {throw new UnsupportedOperationException();}
         @Override public GridCoverage read(GridGeometry d, int... r) {throw new UnsupportedOperationException();}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
index 16b1f60..cbbce51 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
@@ -18,8 +18,7 @@ package org.apache.sis.storage;
 
 import org.opengis.metadata.Metadata;
 import org.opengis.parameter.ParameterValueGroup;
-import org.apache.sis.storage.event.StoreEvent;
-import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.event.StoreListeners;
 
 
 /**
@@ -30,7 +29,7 @@ import org.apache.sis.storage.event.StoreListener;
  * @since   0.8
  * @module
  */
-final strictfp class DataStoreMock extends DataStore {
+public final strictfp class DataStoreMock extends DataStore {
     /**
      * The display name.
      */
@@ -38,8 +37,10 @@ final strictfp class DataStoreMock extends DataStore {
 
     /**
      * Creates a new data store mock with the given display name.
+     *
+     * @param  name  data store display name.
      */
-    DataStoreMock(final String name) {
+    public DataStoreMock(final String name) {
         this.name = name;
     }
 
@@ -51,25 +52,46 @@ final strictfp class DataStoreMock extends DataStore {
         return name;
     }
 
+    /**
+     * Returns {@code null} since there is no open parameters.
+     */
     @Override
     public ParameterValueGroup getOpenParameters() {
         return null;
     }
 
+    /**
+     * Returns {@code null} since there is no metadata.
+     */
     @Override
     public Metadata getMetadata() {
         return null;
     }
 
+    /**
+     * Do nothing.
+     */
     @Override
-    public <T extends StoreEvent> void addListener(StoreListener<? super T> listener, Class<T> eventType) {
+    public void close() {
     }
 
-    @Override
-    public <T extends StoreEvent> void removeListener(StoreListener<? super T> listener, Class<T> eventType) {
+    /**
+     * Opens access to the data store listeners.
+     *
+     * @return the data store listeners.
+     */
+    public StoreListeners listeners() {
+        return listeners;
     }
 
-    @Override
-    public void close() {
+    /**
+     * Sends a pseudo-warning message for testing purpose. This method is defined in this class
+     * for allowing {@link StoreListeners} to detect that the warning come from a data store by
+     * inspecting the stack frame.
+     *
+     * @param  message  the message to send.
+     */
+    public void simulateWarning(String message) {
+        listeners.warning(message);
     }
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java
new file mode 100644
index 0000000..9185879
--- /dev/null
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.event;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import org.apache.sis.storage.DataStoreMock;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link StoreListeners} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final strictfp class StoreListenersTest extends TestCase implements StoreListener<WarningEvent> {
+    /**
+     * Dummy data store used for firing events.
+     */
+    private final DataStoreMock store;
+
+    /**
+     * The warning received by {@link #eventOccured(WarningEvent)}.
+     * This is stored for allowing test methods to verify the properties.
+     */
+    private LogRecord warning;
+
+    /**
+     * Creates a new test case.
+     */
+    public StoreListenersTest() {
+        store = new DataStoreMock("Event emitter");
+    }
+
+    /**
+     * Invoked when a warning occurred. The implementation in this test verifies that the {@code source} property has
+     * the expected values, then stores the log record in the {@link #warning} field for inspection by the test method.
+     *
+     * @param  warning  the warning event emitted by the data store.
+     */
+    @Override
+    public void eventOccured(final WarningEvent warning) {
+        assertSame("source", store, warning.getSource());
+        this.warning = warning.getDescription();
+    }
+
+    /**
+     * Tests {@link StoreListeners#addListener(StoreListener, Class)} followed by
+     * {@link StoreListeners#removeListener(StoreListener, Class)}.
+     */
+    @Test
+    public void testAddAndRemoveStoreListener() {
+        final StoreListeners listeners = store.listeners();
+        assertFalse("hasListeners()", listeners.hasListeners(WarningEvent.class));
+        listeners.addListener(this, WarningEvent.class);
+        assertTrue("hasListeners()", listeners.hasListeners(WarningEvent.class));
+        listeners.removeListener(this, WarningEvent.class);
+        assertFalse("hasListeners()", listeners.hasListeners(WarningEvent.class));
+        listeners.removeListener(this, WarningEvent.class);         // Should be no-op.
+    }
+
+    /**
+     * Tests {@link StoreListeners#warning(String, Exception)} with a registered listener.
+     */
+    @Test
+    @DependsOnMethod("testAddAndRemoveStoreListener")
+    public void testWarning() {
+        final LogRecord record = new LogRecord(Level.WARNING, "The message");
+        store.addListener(this, WarningEvent.class);
+        store.listeners().warning(record);
+        assertSame(record, warning);
+    }
+
+    /**
+     * Tests {@link StoreListeners#warning(String, Exception)} with a registered listener.
+     * This method shall infer the source class name and source method name automatically.
+     */
+    @Test
+    @DependsOnMethod("testWarning")
+    public void testWarningWithAutoSource() {
+        store.addListener(this, WarningEvent.class);
+        store.simulateWarning("The message");
+        assertNotNull("Listener has not been notified.", warning);
+        assertEquals(DataStoreMock.class.getName(), warning.getSourceClassName());
+        assertEquals("simulateWarning", warning.getSourceMethodName());
+        assertEquals("The message", warning.getMessage());
+    }
+}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
index 084d68c..afa697e 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
@@ -43,6 +43,7 @@ import org.junit.BeforeClass;
     org.apache.sis.storage.FeatureNamingTest.class,
     org.apache.sis.storage.ProbeResultTest.class,
     org.apache.sis.storage.StorageConnectorTest.class,
+    org.apache.sis.storage.event.StoreListenersTest.class,
     org.apache.sis.internal.storage.query.SimpleQueryTest.class,
     org.apache.sis.internal.storage.xml.MimeTypeDetectorTest.class,
     org.apache.sis.internal.storage.xml.StoreProviderTest.class,
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
index 24f613c..e7d59e5 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
@@ -125,7 +125,7 @@ public abstract class StaxDataStore extends URIDataStore {
      * instead than creating new streams for re-reading the data.  If we can not reset the stream but can
      * create a new one, then this field will become a reference to the new stream. This change should be
      * done only in last resort, when there is no way to reuse the existing stream. This is because the
-     * streams created by {@link ChannelFactory#inputStream(String, WarningListeners)} are not of the same
+     * streams created by {@link ChannelFactory#inputStream(String, StoreListeners)} are not of the same
      * kind than the streams created by {@link StorageConnector}.</p>
      *
      * @see #close()