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 489bf1c33fbf3512198ce75a53b70a63cff04ca1 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Mar 26 15:13:35 2022 +0100 Allow subclasses to specify that they will not fire any kind of event other than warnings. This commit reproduces a slight optimization which has been removed in previous commit. That optimization was avoiding strong references to unused listeners, but we can not keep that arbitrary filtering anymore if the API goes public. We had to make it explicit opt-in. --- .../apache/sis/storage/landsat/LandsatStore.java | 3 + .../apache/sis/storage/geotiff/GeoTiffStore.java | 3 + .../org/apache/sis/storage/netcdf/NetcdfStore.java | 3 + .../java/org/apache/sis/storage/sql/SQLStore.java | 3 + .../sis/internal/storage/AbstractResource.java | 6 - .../org/apache/sis/internal/storage/Resources.java | 5 + .../sis/internal/storage/Resources.properties | 1 + .../sis/internal/storage/Resources_fr.properties | 1 + .../org/apache/sis/internal/storage/csv/Store.java | 1 + .../apache/sis/internal/storage/folder/Store.java | 1 + .../org/apache/sis/internal/storage/wkt/Store.java | 1 + .../org/apache/sis/internal/storage/xml/Store.java | 1 + .../apache/sis/storage/event/StoreListeners.java | 193 ++++++++++++++++++--- .../sis/storage/event/StoreListenersTest.java | 24 ++- .../org/apache/sis/internal/storage/gpx/Store.java | 1 + 15 files changed, 214 insertions(+), 33 deletions(-) diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java index d2e9508..30fa5eb 100644 --- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java +++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java @@ -152,6 +152,9 @@ public class LandsatStore extends DataStore implements Aggregate { throw new UnsupportedStorageException(super.getLocale(), LandsatStoreProvider.NAME, connector.getStorage(), connector.getOption(OptionKey.OPEN_OPTIONS)); } + if (getClass() == LandsatStore.class) { + listeners.useWarningEventsOnly(); + } } /** 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 8c935fa..e71c44f 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 @@ -208,6 +208,9 @@ public class GeoTiffStore extends DataStore implements Aggregate { } catch (IOException e) { throw new DataStoreException(e); } + if (getClass() == GeoTiffStore.class) { + listeners.useWarningEventsOnly(); + } } /** 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 921315c..407aa55 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 @@ -128,6 +128,9 @@ public class NetcdfStore extends DataStore implements Aggregate { final NameFactory f = decoder.nameFactory; decoder.namespace = f.createNameSpace(f.createLocalName(null, id), null); } + if (getClass() == NetcdfStore.class) { + listeners.useWarningEventsOnly(); + } } /** 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 7d2dd94..0b5bdf1 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 @@ -162,6 +162,9 @@ public class SQLStore extends DataStore implements Aggregate { } this.tableNames = ArraysExt.resize(tableNames, tableCount); this.queries = ArraysExt.resize(queries, queryCount); + if (getClass() == SQLStore.class) { + listeners.useWarningEventsOnly(); + } } /** 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 ef5b9cf..007322b 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 @@ -46,7 +46,6 @@ 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> * * <h2>Thread safety</h2> @@ -59,11 +58,6 @@ import org.apache.sis.xml.NilReason; * @module */ 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. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java index d7d24a0..d222f51 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java @@ -226,6 +226,11 @@ public final class Resources extends IndexedResourceBundle { public static final short FoliationRepresentation = 38; /** + * This resource should not fire events of type “{0}”. + */ + public static final short IllegalEventType_1 = 65; + + /** * The {0} data store does not accept features of type “{1}”. */ public static final short IllegalFeatureType_2 = 7; diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties index ec8ebc2..b450bc4 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties @@ -52,6 +52,7 @@ FeatureNotFound_2 = Feature \u201c{1}\u201d has not been found i FileAlreadyExists_2 = A {1,choice,0#file|1#directory} already exists at \u201c{0}\u201d. FileIsNotAResourceDirectory_1 = The \u201c{0}\u201d file is not a directory of resources. FoliationRepresentation = Whether to assemble trajectory fragments (lines in CSV file) in a single feature instance. +IllegalEventType_1 = This resource should not fire events of type \u201c{0}\u201d. IllegalFeatureType_2 = The {0} data store does not accept features of type \u201c{1}\u201d. IllegalInputTypeForReader_2 = The {0} reader does not accept inputs of type \u2018{1}\u2019. IllegalOutputTypeForWriter_2 = The {0} writer does not accept outputs of type \u2018{1}\u2019. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties index 6cbf683..9c37915 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties @@ -57,6 +57,7 @@ FeatureNotFound_2 = L\u2019entit\u00e9 \u00ab\u202f{1}\u202f\u00 FileAlreadyExists_2 = Un {1,choice,0#fichier|1#r\u00e9pertoire} existe d\u00e9j\u00e0 \u00e0 l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb. FileIsNotAResourceDirectory_1 = Le fichier \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un r\u00e9pertoire de ressources. FoliationRepresentation = Indique s\u2019il faut assembler les fragments de trajectoires (lignes dans un fichier CSV) dans une entit\u00e9 unique. +IllegalEventType_1 = Cette ressource ne devrait pas lancer des \u00e9v\u00e9nements de type \u00ab\u202f{0}\u202f\u00bb. IllegalFeatureType_2 = Le format {0} ne stocke pas de donn\u00e9es de type \u00ab\u202f{1}\u202f\u00bb. IllegalInputTypeForReader_2 = Le lecteur {0} n\u2019accepte pas des entr\u00e9s de type \u2018{1}\u2019. IllegalOutputTypeForWriter_2 = L\u2019encodeur {0} n\u2019accepte pas des sorties de type \u2018{1}\u2019. 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 7d369ce..7bb6765 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 @@ -305,6 +305,7 @@ final class Store extends URIDataStore implements FeatureSet { this.featureType = featureType; this.foliation = foliation; this.dissociate |= (timeEncoding == null); + listeners.useWarningEventsOnly(); } /** 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 d689bcb..2042aac 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 @@ -178,6 +178,7 @@ class Store extends DataStore implements StoreResource, Aggregate, DirectoryStre children = new ConcurrentHashMap<>(); children.put(path.toRealPath(), this); componentProvider = format; + listeners.useWarningEventsOnly(); } /** diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java index 9fd77e3..1c346dc 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java @@ -86,6 +86,7 @@ final class Store extends URIDataStore { objects = new ArrayList<>(); source = connector.commit(Reader.class, StoreProvider.NAME); library = connector.getOption(OptionKey.GEOMETRY_LIBRARY); + listeners.useWarningEventsOnly(); } /** 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 1036f27..659a52f 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 @@ -102,6 +102,7 @@ final class Store extends URIDataStore implements Filter { throw new UnsupportedStorageException(super.getLocale(), StoreProvider.NAME, connector.getStorage(), connector.getOption(OptionKey.OPEN_OPTIONS)); } + listeners.useWarningEventsOnly(); } /** 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 97cfbc3..48d6eea 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 @@ -17,7 +17,10 @@ package org.apache.sis.storage.event; import java.util.Map; +import java.util.Set; +import java.util.HashSet; import java.util.Locale; +import java.util.Collections; import java.util.IdentityHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -30,8 +33,11 @@ import org.apache.sis.util.Exceptions; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.util.collection.Containers; +import org.apache.sis.internal.storage.Resources; import org.apache.sis.internal.storage.StoreResource; import org.apache.sis.internal.storage.StoreUtilities; +import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.storage.DataStoreProvider; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.Resource; @@ -98,6 +104,19 @@ public class StoreListeners implements Localized { private volatile ForType<?> listeners; /** + * All types of of events that may be fired, or {@code null} if no restriction. + * This is a <cite>copy on write</cite> set: no elements are modified after a set has been created. + * + * @see #setUsableEventTypes(Class...) + */ + private volatile Set<Class<? extends StoreEvent>> permittedEventTypes; + + /** + * Frequently used value for {@link #permittedEventTypes}. + */ + private static final Set<Class<? extends StoreEvent>> WARNING_EVENT_TYPE = Collections.singleton(WarningEvent.class); + + /** * All listeners for a given even type. * * @param <T> the type of events of interest to the listeners. @@ -176,6 +195,22 @@ public class StoreListeners implements Localized { } /** + * Removes all listeners which will never receive any kind of events. + * + * Note: ideally we would remove the whole {@code ForType} object, but it would require to rebuild the whole + * {@link #listeners} chain. It is not worth because this method should never be invoked if callers invoked + * the {@link #setUsableEventTypes(Class...)} at construction time (a recommended practice). + */ + static void removeUnreachables(ForType<?> listeners, final Set<Class<? extends StoreEvent>> permittedEventTypes) { + while (listeners != null) { + if (!isPossibleEvent(permittedEventTypes, listeners.type)) { + listeners.listeners = null; + } + listeners = listeners.next; + } + } + + /** * Returns {@code true} if this element has at least one listener. */ final boolean hasListeners() { @@ -213,6 +248,11 @@ public class StoreListeners implements Localized { * will be notified as well as listeners registered in this {@code StoreListeners}. * Each listener will be notified only once even if it has been registered in two places. * + * <h4>Permitted even types</h4> + * If the parent restricts the usable event types to a subset of {@link StoreEvent} subtypes, + * then this {@code StoreListeners} inherits those restrictions. The list of usable types can + * be {@linkplain #setUsableEventTypes rectricted more} but can not be relaxed. + * * @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. */ @@ -220,6 +260,9 @@ public class StoreListeners implements Localized { ArgumentChecks.ensureNonNull("source", source); this.source = source; this.parent = parent; + if (parent != null) { + permittedEventTypes = parent.permittedEventTypes; + } } /** @@ -290,16 +333,8 @@ public class StoreListeners implements Localized { */ @Override public Locale getLocale() { - 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; + final DataStore ds = getDataStore(this); + return (ds != null) ? ds.getLocale() : null; } /** @@ -336,10 +371,30 @@ public class StoreListeners implements Localized { } /** + * Notifies this {@code StoreListeners} that it will fire only {@link WarningEvent}s. This method is a + * shortcut for <code>{@linkplain setUsableEventTypes setUsableEventTypes}(WarningEvent.class)}</code>, + * provided because frequently used by read-only data store implementations. + * + * @see #setUsableEventTypes(Class...) + * @see WarningEvent + * + * @since 1.2 + */ + public synchronized void useWarningEventsOnly() { + final Set<Class<? extends StoreEvent>> current = permittedEventTypes; + if (current == null) { + permittedEventTypes = WARNING_EVENT_TYPE; + } else if (!WARNING_EVENT_TYPE.equals(current)) { + throw illegalEventType(WarningEvent.class); + } + ForType.removeUnreachables(listeners, WARNING_EVENT_TYPE); + } + + /** * Reports a warning described by the given message. * * <p>This method is a shortcut for <code>{@linkplain #warning(Level, String, Exception) - * warning}({@linkplain Level#WARNING}, message, null)</code>. + * warning}({@linkplain Level#WARNING}, null, exception)</code>.</p> * * @param message the warning message to report. */ @@ -355,7 +410,7 @@ public class StoreListeners implements Localized { * See {@linkplain #warning(Level, String, Exception) below} for more explanation. * * <p>This method is a shortcut for <code>{@linkplain #warning(Level, String, Exception) - * warning}({@linkplain Level#WARNING}, null, exception)</code>. + * warning}({@linkplain Level#WARNING}, null, exception)</code>.</p> * * @param exception the exception to report. */ @@ -373,7 +428,7 @@ public class StoreListeners implements Localized { * warnings). See {@linkplain #warning(Level, String, Exception) below} for more explanation. * * <p>This method is a shortcut for <code>{@linkplain #warning(Level, String, Exception) - * warning}({@linkplain Level#WARNING}, message, exception)</code>. + * warning}({@linkplain Level#WARNING}, message, exception)</code>.</p> * * @param message the warning message to report, or {@code null} if none. * @param exception the exception to report, or {@code null} if none. @@ -483,7 +538,8 @@ public class StoreListeners implements Localized { * * @param description warning details provided as a log record. * @param onUnhandled filter invoked if the record has not been handled by a {@link StoreListener}, - * or {@code null} if none. + * or {@code null} if none. This filter determines whether the record should be sent to the + * logger returned by {@link #getLogger()}. * * @since 1.2 */ @@ -514,11 +570,17 @@ public class StoreListeners implements Localized { * @param event the event to fire. * @param eventType the type of events to be fired. * @return {@code true} if the event has been sent to at least one listener. + * @throws IllegalArgumentException if the given event type is not one of the types of events + * that this {@code StoreListeners} can fire. */ @SuppressWarnings("unchecked") public <T extends StoreEvent> boolean fire(final T event, final Class<T> eventType) { ArgumentChecks.ensureNonNull("event", event); ArgumentChecks.ensureNonNull("eventType", eventType); + final Set<Class<? extends StoreEvent>> permittedEventTypes = this.permittedEventTypes; + if (permittedEventTypes != null && !permittedEventTypes.contains(eventType)) { + throw illegalEventType(eventType); + } Map<StoreListener<?>,Boolean> done = null; StoreListeners m = this; do { @@ -533,6 +595,35 @@ public class StoreListeners implements Localized { } /** + * Returns the exception to throw for an event type which is not in the set of permitted types. + */ + private IllegalArgumentException illegalEventType(final Class<?> type) { + return new IllegalArgumentException(Resources.forLocale(getLocale()) + .getString(Resources.Keys.IllegalEventType_1, type)); + } + + /** + * Verifies if a listener interested in the specified type of events could receive some events + * from this {@code StoreListeners}. + * + * @param eventType type of events to listen. + * @return whether a listener could receive events of the specified type. + * + * @see #setUsableEventTypes(Class...) + */ + private static boolean isPossibleEvent(final Set<Class<? extends StoreEvent>> permittedEventTypes, final Class<?> eventType) { + if (permittedEventTypes == null) { + return true; + } + for (final Class<? extends StoreEvent> type : permittedEventTypes) { + if (eventType.isAssignableFrom(type)) { + return true; + } + } + return false; + } + + /** * Registers a listener to notify when the specified kind of event occurs. * 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)} @@ -555,18 +646,20 @@ public class StoreListeners implements Localized { public synchronized <T extends StoreEvent> void addListener(final Class<T> eventType, final StoreListener<? super T> listener) { ArgumentChecks.ensureNonNull("listener", listener); ArgumentChecks.ensureNonNull("eventType", eventType); - ForType<T> ce = null; - for (ForType<?> e = listeners; e != null; e = e.next) { - if (e.type.equals(eventType)) { - ce = (ForType<T>) e; - break; + if (isPossibleEvent(permittedEventTypes, eventType)) { + ForType<T> ce = null; + for (ForType<?> e = listeners; e != null; e = e.next) { + if (e.type.equals(eventType)) { + ce = (ForType<T>) e; + break; + } } + if (ce == null) { + ce = new ForType<>(eventType, listeners); + listeners = ce; + } + ce.add(listener); } - if (ce == null) { - ce = new ForType<>(eventType, listeners); - listeners = ce; - } - ce.add(listener); } /** @@ -604,7 +697,10 @@ public class StoreListeners implements Localized { } /** - * Returns {@code true} if this object or its parent contains at least one listener for the given type of event. + * Returns {@code true} if at least one listener is registered for the given type or a super-type. + * This method may unconditionally return {@code false} if the given type of event is never fired + * by this {@code StoreListeners}, because calls to {@code addListener(eventType, …)} are free to + * ignore the listeners for those types. * * @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. @@ -618,11 +714,56 @@ public class StoreListeners implements Localized { if (e.hasListeners()) { return true; } - break; } } m = m.parent; } while (m != null); return false; } + + /** + * Notifies this {@code StoreListeners} that only events of the specified types will be fired. + * With this knowledge, {@code StoreListeners} will not retain any reference to listeners that + * are not listening to events of those types or to events of a parent type. + * This restriction allows the garbage collector to dispose unnecessary listeners. + * + * <div class="note"><b>Example:</b> + * an application may unconditionally register listeners for being notified about additions of new data. + * If a {@link DataStore} implementation is read-only, then such listeners would never receive any notification. + * As a slight optimization, the {@code DataStore} constructor can invoke this method for example as below: + * + * {@preformat java + * listeners.setUsableEventTypes(WarningEvent.class); + * } + * + * With this configuration, calls to {@code addListener(DataAddedEvent.class, foo)} will be ignored, + * thus avoiding this instance to retain a never-used reference to the {@code foo} listener. + * </div> + * + * The argument shall enumerate all permitted types, including sub-types (they are not automatically accepted). + * All types given in argument must be types that were accepted before the invocation of this method. + * In other words, this method can be invoked for reducing the set of permitted types but not for expanding it. + * + * @param permitted type of events that are permitted. Permitted sub-types shall be explicitly enumerated as well. + * @throws IllegalArgumentException if one of the given types was not permitted before invocation of this method. + * + * @see #useWarningEventsOnly() + * + * @since 1.2 + */ + @SuppressWarnings("unchecked") + public synchronized void setUsableEventTypes(final Class<?>... permitted) { + ArgumentChecks.ensureNonEmpty("permitted", permitted); + final Set<Class<? extends StoreEvent>> current = permittedEventTypes; + final Set<Class<? extends StoreEvent>> types = new HashSet<>(Containers.hashMapCapacity(permitted.length)); + for (final Class<?> type : permitted) { + if (current != null ? current.contains(type) : StoreEvent.class.isAssignableFrom(type)) { + types.add((Class<? extends StoreEvent>) type); + } else { + throw illegalEventType(type); + } + } + permittedEventTypes = WARNING_EVENT_TYPE.equals(types) ? WARNING_EVENT_TYPE : CollectionsExt.compact(types); + ForType.removeUnreachables(listeners, types); + } } 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 index 7d1fc6d..f4af553 100644 --- 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 @@ -30,7 +30,7 @@ import static org.junit.Assert.*; * Tests the {@link StoreListeners} class. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.2 * @since 1.0 * @module */ @@ -81,6 +81,28 @@ public final strictfp class StoreListenersTest extends TestCase implements Store } /** + * Verifies that {@link StoreListeners#addListener(Class, StoreListener)} ignore the given listener + * when the specified type of event is never fired. + */ + @Test + public void testListenerFiltering() { + final StoreListeners listeners = store.listeners(); + listeners.addListener(StoreEvent.class, (event) -> {}); + listeners.addListener(WarningEvent.class, this); + assertTrue(listeners.hasListeners(StoreEvent.class)); + assertTrue(listeners.hasListeners(WarningEvent.class)); + listeners.setUsableEventTypes(StoreEvent.class, WarningEvent.class); + assertTrue(listeners.hasListeners(StoreEvent.class)); + assertTrue(listeners.hasListeners(WarningEvent.class)); + listeners.setUsableEventTypes(StoreEvent.class); + assertTrue (listeners.hasListeners(StoreEvent.class)); + assertFalse(listeners.hasListeners(WarningEvent.class)); + listeners.addListener(WarningEvent.class, this); + assertTrue (listeners.hasListeners(StoreEvent.class)); + assertFalse(listeners.hasListeners(WarningEvent.class)); + } + + /** * Tests {@link StoreListeners#warning(String, Exception)} with a registered listener. */ @Test diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java index 5957a0c..451059e 100644 --- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java +++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java @@ -102,6 +102,7 @@ public final class Store extends StaxDataStore implements FeatureSet { } catch (FactoryException e) { throw new DataStoreException(e); } + listeners.useWarningEventsOnly(); } /**