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 8f1dbbf6df840ad0e5cf8eb9a69288b5218878e6
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Mar 24 17:36:40 2022 +0100

    `AbstractResource` does not inherit anymore from `StoreListeners`.
    Instead, `StoreListeners` become a field.
    This is a step toward moving `AbstractResource` to public API.
---
 .../org/apache/sis/storage/landsat/BandGroup.java  |  2 +-
 .../apache/sis/storage/landsat/package-info.java   |  2 +-
 .../sis/storage/landsat/MetadataReaderTest.java    | 24 +++++++-
 .../org/apache/sis/storage/geotiff/CRSBuilder.java |  6 +-
 .../org/apache/sis/storage/geotiff/DataCube.java   | 11 ++++
 .../apache/sis/storage/geotiff/GeoTiffStore.java   | 17 ++----
 .../sis/storage/geotiff/GridGeometryBuilder.java   |  5 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    | 18 ++++--
 .../sis/storage/geotiff/ImageMetadataBuilder.java  |  5 +-
 .../org/apache/sis/storage/geotiff/Reader.java     |  2 +-
 .../sis/internal/netcdf/DiscreteSampling.java      |  2 +-
 .../apache/sis/internal/netcdf/RasterResource.java | 12 ++--
 .../org/apache/sis/internal/netcdf/TestCase.java   | 26 ++++++++-
 .../internal/netcdf/impl/ChannelDecoderTest.java   |  3 +-
 .../storage/netcdf/NetcdfStoreProviderTest.java    |  5 +-
 .../org/apache/sis/internal/sql/feature/Table.java |  2 +-
 .../sis/internal/storage/AbstractFeatureSet.java   |  5 +-
 .../sis/internal/storage/AbstractGridResource.java | 15 +++--
 .../sis/internal/storage/AbstractResource.java     | 65 +++++++++++++++-------
 .../sis/internal/storage/AggregatedFeatureSet.java |  4 +-
 .../internal/storage/ConcatenatedFeatureSet.java   |  2 +-
 .../org/apache/sis/storage/CoverageSubset.java     |  6 +-
 .../java/org/apache/sis/storage/DataStore.java     |  2 +-
 .../java/org/apache/sis/storage/FeatureSubset.java |  2 +-
 .../main/java/org/apache/sis/storage/Resource.java |  4 +-
 .../apache/sis/storage/event/StoreListeners.java   | 14 +----
 .../internal/storage/AbstractGridResourceTest.java |  4 +-
 27 files changed, 168 insertions(+), 97 deletions(-)

diff --git 
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
 
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
index 3750f28..1ec0a31 100644
--- 
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
+++ 
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/BandGroup.java
@@ -114,7 +114,7 @@ final class BandGroup extends AbstractResource implements 
Aggregate {
     protected Metadata createMetadata() throws DataStoreException {
         final MetadataBuilder metadata = new MetadataBuilder();
         metadata.addTitle(group.title);             // Must be before 
`addDefaultMetadata(…)`.
-        metadata.addDefaultMetadata(this, this);
+        metadata.addDefaultMetadata(this, listeners);
         return metadata.build(true);
     }
 
diff --git 
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
 
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
index 81a1201..2f50ae6 100644
--- 
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
+++ 
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
@@ -27,7 +27,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
diff --git 
a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
 
b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
index fe050d3..5355e5d 100644
--- 
a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
+++ 
b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
@@ -35,6 +35,7 @@ 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.storage.event.StoreListeners;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -79,7 +80,7 @@ public class MetadataReaderTest extends TestCase {
         try (BufferedReader in = new BufferedReader(new InputStreamReader(
                 
MetadataReaderTest.class.getResourceAsStream("LandsatTest.txt"), "UTF-8")))
         {
-            final MetadataReader reader = new MetadataReader(null, 
"LandsatTest.txt", new AbstractResource(null));
+            final MetadataReader reader = new MetadataReader(null, 
"LandsatTest.txt", createListeners());
             reader.read(in);
             actual = reader.getMetadata();
         }
@@ -254,4 +255,25 @@ public class MetadataReaderTest extends TestCase {
 
         verifier.assertMetadataEquals();
     }
+
+    /**
+     * Creates a dummy set of store listeners.
+     * Used only for constructors that require a non-null {@link 
StoreListeners} instance.
+     *
+     * @return a dummy set of listeners.
+     */
+    private static StoreListeners createListeners() {
+        final class DummyResource extends AbstractResource {
+            /** Creates a dummy resource without parent. */
+            DummyResource() {
+                super(null);
+            }
+
+            /** Makes listeners accessible to this package. */
+            StoreListeners listeners() {
+                return listeners;
+            }
+        }
+        return new DummyResource().listeners();
+    }
 }
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
index 5bfc184..5781ee6 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
@@ -432,7 +432,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
                 try {
                     expected = Integer.parseInt(id.getCode());
                 } catch (NumberFormatException e) {
-                    reader.store.warning(null, e);                  // Should 
not happen.
+                    reader.store.listeners().warning(e);            // Should 
not happen.
                     return;
                 }
                 if (code != expected) {
@@ -562,7 +562,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
         if (epsg != null) try {
             return getCSAuthorityFactory().createCartesianCS(epsg.toString());
         } catch (NoSuchAuthorityCodeException e) {
-            reader.store.warning(null, e);
+            reader.store.listeners().warning(e);
         }
         return (CartesianCS) CoordinateSystems.replaceLinearUnit(cs, unit);
     }
@@ -580,7 +580,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
         if (epsg != null) try {
             return 
getCSAuthorityFactory().createEllipsoidalCS(epsg.toString());
         } catch (NoSuchAuthorityCodeException e) {
-            reader.store.warning(null, e);
+            reader.store.listeners().warning(e);
         }
         return (EllipsoidalCS) CoordinateSystems.replaceAngularUnit(cs, unit);
     }
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
index 8bcefef..7cfa2cc 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.geotiff;
 
+import java.util.Locale;
 import java.nio.file.Path;
 import java.awt.image.DataBuffer;
 import java.awt.image.SampleModel;
@@ -79,6 +80,16 @@ abstract class DataCube extends TiledGridResource implements 
ResourceOnFileSyste
     }
 
     /**
+     * Returns the locale for warnings and error messages.
+     *
+     * <p><b>Warning:</b> do not implement {@link 
org.apache.sis.util.Localized},
+     * as it may cause an infinite loop in {@code listeners.getLocale()} 
call.</p>
+     */
+    final Locale getLocale() {
+        return listeners.getLocale();
+    }
+
+    /**
      * Shortcut for a frequently requested information.
      */
     final String filename() {
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 779c92d..8c935fa 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
@@ -237,6 +237,8 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
 
     /**
      * Opens access to listeners for {@link ImageFileDirectory}.
+     *
+     * @see #warning(LogRecord)
      */
     final StoreListeners listeners() {
         return listeners;
@@ -517,20 +519,9 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
     }
 
     /**
-     * Reports a warning represented by the given message and exception.
-     * At least one of {@code message} and {@code exception} shall be non-null.
-     *
-     * @param message    the message to log, or {@code null} if none.
-     * @param exception  the exception to log, or {@code null} if none.
-     */
-    final void warning(final String message, final Exception exception) {
-        listeners.warning(message, exception);
-    }
-
-    /**
      * Reports a warning contained in the given {@link LogRecord}.
      * Note that the given record will not necessarily be sent to the logging 
framework;
-     * if the user as registered at least one listener, then the record will 
be sent to the listeners instead.
+     * if the user has registered at least one listener, then the record will 
be sent to the listeners instead.
      *
      * <p>This method sets the {@linkplain 
LogRecord#setSourceClassName(String) source class name} and
      * {@linkplain LogRecord#setSourceMethodName(String) source method name} 
to hard-coded values.
@@ -538,6 +529,8 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
      * in this class. We do not report private classes or methods as the 
source of warnings.</p>
      *
      * @param  record  the warning to report.
+     *
+     * @see #listeners()
      */
     final void warning(final LogRecord record) {
         // Logger name will be set by listeners.warning(record).
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
index 461464f..f4508ab 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
@@ -274,7 +274,7 @@ final class GridGeometryBuilder extends GeoKeysLoader {
                 if (e instanceof NoSuchAuthorityCodeException) {
                     key = Resources.Keys.UnknownCRS_1;
                 }
-                reader.store.warning(reader.resources().getString(key, 
reader.store.getDisplayName()), e);
+                
reader.store.listeners().warning(reader.resources().getString(key, 
reader.store.getDisplayName()), e);
             } catch (IllegalArgumentException | NoSuchElementException | 
ClassCastException e) {
                 if (!helper.alreadyReported) {
                     canNotCreate(reader, e);
@@ -380,6 +380,7 @@ final class GridGeometryBuilder extends GeoKeysLoader {
      * Logs a warning telling that we can not create a grid geometry for the 
given reason.
      */
     private static void canNotCreate(final Reader reader, final Exception e) {
-        
reader.store.warning(reader.resources().getString(Resources.Keys.CanNotComputeGridGeometry_1,
 reader.input.filename), e);
+        reader.store.listeners().warning(reader.resources().getString(
+                Resources.Keys.CanNotComputeGridGeometry_1, 
reader.input.filename), e);
     }
 }
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 be48624..9ad8b50 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
@@ -1341,14 +1341,14 @@ final class ImageFileDirectory extends DataCube {
             if (gridGeometry.isDefined(GridGeometry.ENVELOPE)) try {
                 metadata.addExtent(gridGeometry.getEnvelope());
             } catch (TransformException e) {
-                warning(e);
+                listeners.warning(e);
             }
             referencing.completeMetadata(gridGeometry, metadata);
         }
         /*
          * End of metadata construction from TIFF tags.
          */
-        metadata.finish(this);
+        metadata.finish(this, listeners);
         final DefaultMetadata md = metadata.build(false);
         if (isIndexValid) {
             final Metadata c = reader.store.customizer.customize(index, md);
@@ -1698,14 +1698,24 @@ final class ImageFileDirectory extends DataCube {
 
     /**
      * Reports a warning with a message created from the given resource keys 
and parameters.
+     * Note that the log record will not necessarily be sent to the logging 
framework;
+     * if the user has registered at least one listener, then the record will 
be sent to the listeners instead.
+     *
+     * <p>This method sets the {@linkplain 
LogRecord#setSourceClassName(String) source class name} and
+     * {@linkplain LogRecord#setSourceMethodName(String) source method name} 
to hard-coded values.
+     * Those values assume that the warnings occurred indirectly from a call 
to {@link GeoTiffStore#components()}.
+     * We do not report private classes or methods as the source of 
warnings.</p>
      *
      * @param  level       the logging level for the message to log.
      * @param  key         the {@code Resources} key of the message to format.
      * @param  parameters  the parameters to put in the message.
      */
     private void warning(final Level level, final short key, final Object... 
parameters) {
-        final LogRecord r = reader.resources().getLogRecord(level, key, 
parameters);
-        reader.store.warning(r);
+        final LogRecord record = reader.resources().getLogRecord(level, key, 
parameters);
+        record.setSourceClassName(GeoTiffStore.class.getName());
+        record.setSourceMethodName("components()");
+        // Logger name will be set by listeners.warning(record).
+        listeners.warning(record);
     }
 
     /**
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
index 22db2a8..8870d00 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
@@ -21,6 +21,7 @@ import javax.measure.quantity.Length;
 import org.apache.sis.internal.geotiff.Resources;
 import org.apache.sis.internal.geotiff.Compression;
 import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
@@ -168,7 +169,7 @@ final class ImageMetadataBuilder extends MetadataBuilder {
      *
      * @throws DataStoreException if an error occurred while reading metadata 
from the data store.
      */
-    void finish(final ImageFileDirectory image) throws DataStoreException {
+    void finish(final ImageFileDirectory image, final StoreListeners 
listeners) throws DataStoreException {
         image.getIdentifier().ifPresent((id) -> addTitle(id.toString()));
         /*
          * Add information about the file format.
@@ -228,7 +229,7 @@ final class ImageMetadataBuilder extends MetadataBuilder {
         while (complement != null) try {
             complement = complement.appendTo(this);
         } catch (Exception ex) {
-            
image.warning(image.reader.errors().getString(Errors.Keys.CanNotSetPropertyValue_1,
 complement.tag()), ex);
+            
listeners.warning(image.reader.errors().getString(Errors.Keys.CanNotSetPropertyValue_1,
 complement.tag()), ex);
         }
     }
 }
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
index 3af73e0..c770d60 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
@@ -449,7 +449,7 @@ final class Reader extends GeoTIFF {
             exception = null;
         }
         args[0] = Tags.name(tag);
-        store.warning(errors().getString(key, args), exception);
+        store.listeners().warning(errors().getString(key, args), exception);
     }
 
     /**
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 affc0f8..1302cb7 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
@@ -79,6 +79,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, 
getSourceName());
+        return 
Errors.getResources(listeners.getLocale()).getString(Errors.Keys.CanNotRead_1, 
listeners.getSourceName());
     }
 }
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
index db1ece4..499f55e 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
@@ -401,7 +401,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
         if (title != null && !title.isEmpty()) {
             
metadata.addTitle(CharSequences.camelCaseToSentence(title).toString());
         }
-        metadata.addDefaultMetadata(this, this);
+        metadata.addDefaultMetadata(this, listeners);
         return metadata.build(true);
     }
 
@@ -525,7 +525,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
              * If we failed to do that, we will not add quantitative category. 
But we still can add
              * qualitative categories for "no data" sample values in the rest 
of this method.
              */
-            warning(e);
+            listeners.warning(e);
         }
         /*
          * Adds the "missing value" or "fill value" as qualitative categories. 
 If a value has both roles, use "missing value"
@@ -584,7 +584,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
              */
             builder.categories().clear();
             sd = builder.build();
-            warning(e);
+            listeners.warning(e);
         }
         return sd;
     }
@@ -630,7 +630,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
             for (int i=0; i<rangeIndices.getNumBands(); i++) {
                 final Variable variable = data[rangeIndices.getSourceIndex(i)];
                 if (!dataType.equals(variable.getDataType())) {
-                    throw new 
DataStoreContentException(Resources.forLocale(getLocale()).getString(
+                    throw new 
DataStoreContentException(Resources.forLocale(listeners.getLocale()).getString(
                             Resources.Keys.MismatchedVariableType_3, 
getFilename(), first.getName(), variable.getName()));
                 }
             }
@@ -723,7 +723,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
          * Provide to `Raster` all information needed for building a 
`RenderedImage` when requested.
          */
         if (imageBuffer == null) {
-            throw new 
DataStoreContentException(Errors.getResources(getLocale()).getString(Errors.Keys.UnsupportedType_1,
 dataType.name()));
+            throw new 
DataStoreContentException(Errors.getResources(listeners.getLocale()).getString(Errors.Keys.UnsupportedType_1,
 dataType.name()));
         }
         final Variable main = data[visibleBand];
         final Raster raster = new Raster(targetDomain, 
UnmodifiableArrayList.wrap(bands), imageBuffer,
@@ -737,7 +737,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
      * Returns the name of the netCDF file. This is used for error messages.
      */
     private String getFilename() {
-        return (location != null) ? location.getFileName().toString() : 
getSourceName();
+        return (location != null) ? location.getFileName().toString() : 
listeners.getSourceName();
     }
 
     /**
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 fc8365a..86f8c4b 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
@@ -26,6 +26,7 @@ import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
 import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.storage.event.StoreListeners;
 import org.opengis.test.dataset.TestData;
 import ucar.nc2.dataset.NetcdfDataset;
 import ucar.nc2.NetcdfFile;
@@ -41,7 +42,7 @@ import static org.junit.Assert.*;
  * <p>This class is <strong>not</strong> thread safe - do not run subclasses 
in parallel.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.2
  * @since   0.3
  * @module
  */
@@ -110,7 +111,28 @@ 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, new AbstractResource(null));
+        return new DecoderWrapper(new NetcdfDataset(createUCAR(file)), 
GeometryLibrary.JAVA2D, createListeners());
+    }
+
+    /**
+     * Creates a dummy set of store listeners.
+     * Used only for constructors that require a non-null {@link 
StoreListeners} instance.
+     *
+     * @return a dummy set of listeners.
+     */
+    protected static StoreListeners createListeners() {
+        final class DummyResource extends AbstractResource {
+            /** Creates a dummy resource without parent. */
+            DummyResource() {
+                super(null);
+            }
+
+            /** Makes listeners accessible to this package. */
+            StoreListeners listeners() {
+                return listeners;
+            }
+        }
+        return new DummyResource().listeners();
     }
 
     /**
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 a18cf5e..d426ecd 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,7 +22,6 @@ 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;
@@ -71,6 +70,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, new 
AbstractResource(null));
+        return new ChannelDecoder(input, null, GeometryLibrary.JAVA2D, 
createListeners());
     }
 }
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 8acae77..ac33a21 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,7 +23,6 @@ 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;
@@ -92,7 +91,7 @@ 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(new 
AbstractResource(null), c)) {
+        try (Decoder decoder = NetcdfStoreProvider.decoder(createListeners(), 
c)) {
             assertInstanceOf("decoder", ChannelDecoder.class, decoder);
         }
     }
@@ -107,7 +106,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(new 
AbstractResource(null), c)) {
+        try (Decoder decoder = NetcdfStoreProvider.decoder(createListeners(), 
c)) {
             assertInstanceOf("decoder", DecoderWrapper.class, decoder);
         }
     }
diff --git 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
index 7e5d027..110d629 100644
--- 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
+++ 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
@@ -194,7 +194,7 @@ final class Table extends AbstractFeatureSet {
      *       A starting point is {@link 
org.apache.sis.storage.FeatureQuery#expectedType(FeatureType)}.
      */
     Table(final Table parent) {
-        super(parent);
+        super(parent.listeners);
         database = parent.database;
         query    = parent.query;
         name     = parent.name;
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 6062a9c..49ae01f 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
@@ -56,8 +56,7 @@ public abstract class AbstractFeatureSet extends 
AbstractResource implements Fea
      * Creates a new resource.
      *
      * @param  parent  listeners of the parent resource, or {@code null} if 
none.
-     *         This is usually the listeners of the {@link 
org.apache.sis.storage.DataStore}
-     *         that created this resource.
+     *         This is usually the listeners of the {@link DataStore} that 
created this resource.
      */
     protected AbstractFeatureSet(final StoreListeners parent) {
         super(parent);
@@ -103,7 +102,7 @@ public abstract class AbstractFeatureSet extends 
AbstractResource implements Fea
     @Override
     protected Metadata createMetadata() throws DataStoreException {
         final MetadataBuilder builder = new MetadataBuilder();
-        builder.addDefaultMetadata(this, this);
+        builder.addDefaultMetadata(this, listeners);
         return builder.build(true);
     }
 }
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 0aaf6c3..dca3626 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
@@ -85,8 +85,7 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
      * Creates a new resource.
      *
      * @param  parent  listeners of the parent resource, or {@code null} if 
none.
-     *         This is usually the listeners of the {@link 
org.apache.sis.storage.DataStore}
-     *         that created this resource.
+     *         This is usually the listeners of the {@link DataStore} that 
created this resource.
      */
     protected AbstractGridResource(final StoreListeners parent) {
         super(parent);
@@ -123,7 +122,7 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
     @Override
     protected Metadata createMetadata() throws DataStoreException {
         final MetadataBuilder builder = new MetadataBuilder();
-        builder.addDefaultMetadata(this, this);
+        builder.addDefaultMetadata(this, listeners);
         return builder.build(true);
     }
 
@@ -154,7 +153,7 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
             for (int i=0; i<range.length; i++) {
                 final int r = range[i];
                 if (r < 0 || r >= numSampleDimensions) {
-                    throw new 
IllegalArgumentException(Resources.forLocale(getLocale()).getString(
+                    throw new 
IllegalArgumentException(Resources.forLocale(listeners.getLocale()).getString(
                             Resources.Keys.InvalidSampleDimensionIndex_2, 
numSampleDimensions - 1, r));
                 }
                 packed[i] = (((long) r) << Integer.SIZE) | i;
@@ -169,7 +168,7 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
                 // Never negative because of check in previous loop.
                 final int r = (int) (packed[i] >>> Integer.SIZE);
                 if (r == previous) {
-                    throw new 
IllegalArgumentException(Resources.forLocale(getLocale()).getString(
+                    throw new 
IllegalArgumentException(Resources.forLocale(listeners.getLocale()).getString(
                             Resources.Keys.DuplicatedSampleDimensionIndex_1, 
r));
                 }
                 previous = r;
@@ -532,13 +531,13 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
      * @param  startTime   value of {@link System#nanoTime()} when the loading 
process started.
      */
     protected final void logReadOperation(final Object file, final 
GridGeometry domain, final long startTime) {
-        final Logger logger = getLogger();
+        final Logger logger = listeners.getLogger();
         final long   nanos  = System.nanoTime() - startTime;
         final Level  level  = PerformanceLevel.forDuration(nanos, 
TimeUnit.NANOSECONDS);
         if (logger.isLoggable(level)) {
-            final Locale locale = getLocale();
+            final Locale locale = listeners.getLocale();
             final Object[] parameters = new Object[6];
-            parameters[0] = IOUtilities.filename(file != null ? file : 
getSourceName());
+            parameters[0] = IOUtilities.filename(file != null ? file : 
listeners.getSourceName());
             parameters[5] = nanos / (double) 
StandardDateFormat.NANOS_PER_SECOND;
             JDK9.ifPresentOrElse(domain.getGeographicExtent(), (box) -> {
                 final AngleFormat f = new AngleFormat(locale);
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 a16efba..ef5b9cf 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,6 +16,7 @@
  */
 package org.apache.sis.internal.storage;
 
+import java.util.Locale;
 import java.util.Optional;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
@@ -26,11 +27,11 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.referencing.CRS;
 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;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.xml.NilReason;
@@ -45,11 +46,9 @@ import org.apache.sis.xml.NilReason;
  *   <li>{@link #getEnvelope()} (recommended)</li>
  *   <li>{@link #createMetadata()} (optional)</li>
  *   <li>{@link #getSynchronizationLock()} (optional)</li>
+ *   <li>{@link #addListener(Class, StoreListener)} (if this resource is 
writable)</li>
  * </ul>
  *
- * This class extends {@link StoreListeners} for convenience reasons.
- * This implementation details may change in any future SIS version.
- *
  * <h2>Thread safety</h2>
  * Default methods of this abstract class are thread-safe.
  * Synchronization, when needed, uses {@link #getSynchronizationLock()}.
@@ -59,7 +58,18 @@ import org.apache.sis.xml.NilReason;
  * @since   0.8
  * @module
  */
-public class AbstractResource extends StoreListeners implements Resource {
+public abstract class AbstractResource implements Resource {
+    /*
+     * Warning: do not implement `org.apache.sis.util.Localized` as it
+     * may cause an infinite loop in calls to `listeners.getLocale()`.
+     */
+
+    /**
+     * The set of registered {@link StoreListener}s for this resources.
+     * This {@code StoreListeners} while typically have {@link 
DataStore#listeners} has a parent.
+     */
+    protected final StoreListeners listeners;
+
     /**
      * A description of this resource as an unmodifiable metadata, or {@code 
null} if not yet computed.
      * If non-null, this metadata should contain at least the resource 
{@linkplain #getIdentifier() identifier}.
@@ -72,11 +82,10 @@ public class AbstractResource extends StoreListeners 
implements Resource {
      * but the listeners of the data store that created this resource will be 
notified as well.
      *
      * @param  parent  listeners of the parent resource, or {@code null} if 
none.
-     *         This is usually the listeners of the {@link 
org.apache.sis.storage.DataStore}
-     *         that created this resource.
+     *         This is usually the listeners of the {@link DataStore} that 
created this resource.
      */
-    public AbstractResource(final StoreListeners parent) {
-        super(parent, null);
+    protected AbstractResource(final StoreListeners parent) {
+        listeners = new StoreListeners(parent, this);
     }
 
     /**
@@ -142,7 +151,7 @@ public class AbstractResource extends StoreListeners 
implements Resource {
      */
     protected Metadata createMetadata() throws DataStoreException {
         final MetadataBuilder builder = new MetadataBuilder();
-        builder.addDefaultMetadata(this, this);
+        builder.addDefaultMetadata(this, listeners);
         return builder.build(true);
     }
 
@@ -157,6 +166,7 @@ public class AbstractResource extends StoreListeners 
implements Resource {
 
     /**
      * Returns the object on which to perform synchronizations for 
thread-safety.
+     * The default implementation returns {@code this}.
      *
      * @return the synchronization lock.
      */
@@ -165,15 +175,29 @@ public class AbstractResource extends StoreListeners 
implements Resource {
     }
 
     /**
-     * Registers only listeners for {@link WarningEvent}s on the assumption 
that most resources
-     * (at least the read-only ones) produce no change events.
+     * Registers a listener to notify when the specified kind of event occurs 
in this resource or in children.
+     * The default implementation forwards to <code>{@linkplain 
#listeners}.addListener(eventType, listener)</code>.
+     *
+     * @param  <T>        compile-time value of the {@code eventType} argument.
+     * @param  listener   listener to notify about events.
+     * @param  eventType  type of {@link StoreEvent}s to listen (can not be 
{@code null}).
      */
     @Override
     public <T extends StoreEvent> void addListener(Class<T> eventType, 
StoreListener<? super T> listener) {
-        // If an argument is null, we let the parent class throws (indirectly) 
NullArgumentException.
-        if (listener == null || eventType == null || 
eventType.isAssignableFrom(WarningEvent.class)) {
-            super.addListener(eventType, listener);
-        }
+        listeners.addListener(eventType, listener);
+    }
+
+    /**
+     * Unregisters a listener previously added to this resource for the given 
type of events.
+     * The default implementation forwards to <code>{@linkplain 
#listeners}.removeListener(eventType, listener)</code>
+     *
+     * @param  <T>        compile-time value of the {@code eventType} argument.
+     * @param  listener   listener to stop notifying about events.
+     * @param  eventType  type of {@link StoreEvent}s which were listened (can 
not be {@code null}).
+     */
+    @Override
+    public <T extends StoreEvent> void removeListener(Class<T> eventType, 
StoreListener<? super T> listener) {
+        listeners.removeListener(eventType, listener);
     }
 
     /**
@@ -186,7 +210,8 @@ public class AbstractResource extends StoreListeners 
implements Resource {
      * @return the message to provide in an exception.
      */
     final String createExceptionMessage(final String filename, Envelope 
request) {
-        String message = 
Errors.getResources(getLocale()).getString(Errors.Keys.CanNotRead_1, filename);
+        final Locale locale = listeners.getLocale();
+        String message = 
Errors.getResources(locale).getString(Errors.Keys.CanNotRead_1, filename);
         if (request != null) try {
             Envelope envelope = getEnvelope().orElse(null);
             if (envelope != null) {
@@ -205,7 +230,7 @@ public class AbstractResource extends StoreListeners 
implements Resource {
                     if (rmax < vmin || rmin > vmax) {
                         final String axis;
                         if (crs != null) {
-                            axis = 
IdentifiedObjects.getDisplayName(crs.getCoordinateSystem().getAxis(i), 
getLocale());
+                            axis = 
IdentifiedObjects.getDisplayName(crs.getCoordinateSystem().getAxis(i), locale);
                         } else if (i < 3) {
                             axis = String.valueOf((char) ('x' + i));
                         } else {
@@ -214,7 +239,7 @@ public class AbstractResource extends StoreListeners 
implements Resource {
                         if (buffer == null) {
                             buffer = new StringBuilder(message);
                         }
-                        buffer.append(System.lineSeparator()).append(" • 
").append(Resources.forLocale(getLocale())
+                        buffer.append(System.lineSeparator()).append(" • 
").append(Resources.forLocale(locale)
                                 
.getString(Resources.Keys.RequestOutOfBounds_5, axis, vmin, vmax, rmin, rmax));
                     }
                 }
@@ -223,7 +248,7 @@ public class AbstractResource extends StoreListeners 
implements Resource {
                 }
             }
         } catch (DataStoreException | TransformException e) {
-            Logging.ignorableException(getLogger(), AbstractResource.class, 
"createExceptionMessage", e);
+            Logging.ignorableException(listeners.getLogger(), 
AbstractResource.class, "createExceptionMessage", e);
         }
         return message;
     }
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 9d9ad80..184f715 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
@@ -125,7 +125,7 @@ abstract class AggregatedFeatureSet extends 
AbstractFeatureSet {
                 if (getEnvelopes(envelopes)) try {
                     envelope = 
ImmutableEnvelope.castOrCopy(Envelopes.union(envelopes.toArray(new 
Envelope[envelopes.size()])));
                 } catch (TransformException e) {
-                    warning(e);
+                    listeners.warning(e);
                 }
                 isEnvelopeComputed = true;
             }
@@ -144,7 +144,7 @@ abstract class AggregatedFeatureSet extends 
AbstractFeatureSet {
     @Override
     protected Metadata createMetadata() throws DataStoreException {
         final MetadataBuilder metadata = new MetadataBuilder();
-        metadata.addDefaultMetadata(this, this);
+        metadata.addDefaultMetadata(this, listeners);
         for (final FeatureSet fs : dependencies()) {
             final FeatureType type = fs.getType();
             metadata.addSource(fs.getMetadata(), ScopeCode.FEATURE_TYPE,
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 8e0d0dc..0428c6d 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
@@ -71,7 +71,7 @@ public class ConcatenatedFeatureSet extends 
AggregatedFeatureSet {
      * but different sources. This is used for creating {@linkplain 
#subset(Query) subsets}.
      */
     private ConcatenatedFeatureSet(final FeatureSet[] sources, final 
ConcatenatedFeatureSet original) {
-        super(original);
+        super(original.listeners);
         this.sources = UnmodifiableArrayList.wrap(sources);
         commonType = original.commonType;
     }
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java
index 4c6aba3..fd8ebd6 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java
@@ -115,8 +115,8 @@ final class CoverageSubset extends AbstractGridResource {
             }
             return derivation.subgrid(areaOfInterest).build();
         } catch (IllegalArgumentException | IllegalStateException e) {
-            final String msg = Resources.forLocale(getLocale())
-                    .getString(Resources.Keys.CanNotIntersectDataWithQuery_1, 
getSourceName());
+            final String msg = Resources.forLocale(listeners.getLocale())
+                    .getString(Resources.Keys.CanNotIntersectDataWithQuery_1, 
listeners.getSourceName());
             final Throwable cause = e.getCause();
             if (cause instanceof FactoryException || cause instanceof 
TransformException) {
                 throw new DataStoreReferencingException(msg, cause);
@@ -222,7 +222,7 @@ final class CoverageSubset extends AbstractGridResource {
      * @param index  the index which is out of bounds.
      */
     private String invalidRange(final int size, final int index) {
-        return Resources.forLocale(getLocale()).getString(
+        return Resources.forLocale(listeners.getLocale()).getString(
                 Resources.Keys.InvalidSampleDimensionIndex_2, size - 1, index);
     }
 }
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 ffc73b6..2237b29 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
@@ -238,7 +238,7 @@ public abstract class DataStore implements Resource, 
Localized, AutoCloseable {
     // See class javadoc for a note on synchronization.
 
     /**
-     * The locale to use for formatting warnings and other messages. This 
locale if for user interfaces
+     * The locale to use for formatting warnings and other messages. This 
locale is for user interfaces
      * only – it has no effect on the data to be read or written from/to the 
data store.
      *
      * <p>The default value is the {@linkplain Locale#getDefault() system 
default locale}.</p>
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
index 5ee4507..c0cfb4e 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
@@ -80,7 +80,7 @@ final class FeatureSubset extends AbstractFeatureSet {
             try {
                 resultType = query.expectedType(type);
             } catch (IllegalArgumentException e) {
-                throw new 
DataStoreContentException(Resources.forLocale(getLocale())
+                throw new 
DataStoreContentException(Resources.forLocale(listeners.getLocale())
                         
.getString(Resources.Keys.CanNotDeriveTypeFromFeature_1, type.getName()), e);
             }
         }
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
index 0ed22e5..42010c3 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
@@ -164,7 +164,7 @@ public interface Resource {
      *
      * @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}).
+     * @param  eventType  type of {@link StoreEvent}s to listen (can not be 
{@code null}).
      */
     <T extends StoreEvent> void addListener(Class<T> eventType, 
StoreListener<? super T> listener);
 
@@ -186,7 +186,7 @@ public interface Resource {
      *
      * @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}).
+     * @param  eventType  type of {@link StoreEvent}s which were listened (can 
not be {@code null}).
      */
     <T extends StoreEvent> void removeListener(Class<T> eventType, 
StoreListener<? super T> listener);
 }
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 30ce46a..97cfbc3 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
@@ -32,7 +32,6 @@ import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.storage.StoreResource;
 import org.apache.sis.internal.storage.StoreUtilities;
-import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.storage.DataStoreProvider;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.Resource;
@@ -218,16 +217,7 @@ public class StoreListeners implements Localized {
      * @param source  the source of events. Can not be null.
      */
     public StoreListeners(final StoreListeners parent, Resource 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 AbstractResource) {
-            source = (Resource) this;
-        } else {
-            ArgumentChecks.ensureNonNull("source", source);
-        }
+        ArgumentChecks.ensureNonNull("source", source);
         this.source = source;
         this.parent = parent;
     }
@@ -235,7 +225,7 @@ public class StoreListeners implements Localized {
     /**
      * Returns the source of events. This value is specified at construction 
time.
      *
-     * @return the source of events. Never {@code null} but may be {@code 
this}.
+     * @return the source of events (never {@code null}).
      */
     public Resource getSource() {
         return source;
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 37bfeea..11b85cd 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
@@ -31,7 +31,7 @@ import static org.junit.Assert.*;
  * Tests {@link AbstractGridResource} and {@link 
AbstractGridResource.RangeArgument}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.2
  * @since   1.0
  * @module
  */
@@ -39,7 +39,7 @@ public final strictfp class AbstractGridResourceTest  extends 
TestCase {
     /**
      * A resource performing no operation.
      */
-    private final AbstractGridResource resource = new 
AbstractGridResource((AbstractGridResource) null) {
+    private final AbstractGridResource resource = new 
AbstractGridResource(null) {
         @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();}

Reply via email to