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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 32d24eaa26 Restore the deferred object creation in
`IdentifiedObjectFinder` and the omission of database scanning when a match is
found early.
32d24eaa26 is described below
commit 32d24eaa261ddc9a06210d561dcd5c47a406db17
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Aug 29 15:21:16 2025 +0200
Restore the deferred object creation in `IdentifiedObjectFinder`
and the omission of database scanning when a match is found early.
---
.../apache/sis/referencing/AuthorityFactories.java | 19 +-
.../apache/sis/referencing/IdentifiedObjects.java | 9 +-
.../factory/AuthorityFactoryIdentifier.java | 19 +-
.../factory/IdentifiedObjectFinder.java | 554 ++++++++++++++-------
.../factory/MultiAuthoritiesFactory.java | 106 +++-
.../referencing/factory/sql/EPSGCodeFinder.java | 31 +-
.../referencing/factory/sql/EPSGDataAccess.java | 9 +-
.../sis/referencing/factory/sql/TableInfo.java | 4 +-
.../referencing/internal/EPSGParameterDomain.java | 4 +-
.../sis/referencing/internal/MergedProperties.java | 4 +-
.../operation/CoordinateOperationRegistry.java | 2 +
.../sis/referencing/privy/FilteredIterator.java | 113 +++++
.../org/apache/sis/referencing/privy/LazySet.java | 25 +-
.../factory/ConcurrentAuthorityFactoryTest.java | 2 +-
.../factory/IdentifiedObjectFinderTest.java | 3 +-
.../referencing/factory/sql/EPSGFactoryTest.java | 24 +-
.../sis/storage/sql/feature/InfoStatements.java | 8 +-
.../apache/sis/util/privy/SetOfUnknownSize.java | 6 +-
netbeans-project/nbproject/project.xml | 1 +
.../apache/sis/resources/embedded/Generator.java | 2 +-
20 files changed, 671 insertions(+), 274 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
index c20937e0a6..287bd3fadb 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
@@ -42,6 +42,7 @@ import
org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
import org.apache.sis.referencing.factory.UnavailableFactoryException;
import org.apache.sis.referencing.factory.sql.EPSGFactory;
+import org.apache.sis.referencing.privy.FilteredIterator;
import org.apache.sis.util.logging.Logging;
@@ -121,7 +122,9 @@ final class AuthorityFactories<T extends AuthorityFactory>
extends LazySet<T> {
Reflect.log(AuthorityFactories.class, "createSourceIterator", e);
loader = ServiceLoader.load(service);
}
- return loader.iterator();
+ // Excludes the `EPSGFactoryProxy` instance.
+ return new FilteredIterator<>(loader.iterator(),
+ (element) -> (element instanceof EPSGFactoryProxy) ? null :
element);
}
/**
@@ -137,20 +140,6 @@ final class AuthorityFactories<T extends AuthorityFactory>
extends LazySet<T> {
return (T[]) new GeodeticAuthorityFactory[] {getEPSG(true)};
}
- /**
- * Invoked by {@link LazySet} for fetching the next element from the given
iterator.
- * Skips the {@link EPSGFactoryProxy} if possible, or returns {@code null}
otherwise.
- * Note that {@link MultiAuthoritiesFactory} is safe to null values.
- */
- @Override
- protected T next(final Iterator<? extends T> it) {
- T e = it.next();
- if (e instanceof EPSGFactoryProxy) {
- e = it.hasNext() ? it.next() : null;
- }
- return e;
- }
-
/**
* Sets the EPSG factory to the given value.
*
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index 6ea0d83d3d..9822787bbf 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@ -46,6 +46,7 @@ import org.apache.sis.util.privy.Strings;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.privy.DefinitionURI;
import org.apache.sis.util.privy.CollectionsExt;
+import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.pending.jdk.JDK21;
import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.metadata.privy.Identifiers;
@@ -582,7 +583,7 @@ public final class IdentifiedObjects extends Static {
final IdentifiedObjectFinder finder)
throws FactoryException
{
String urn = null;
- if (object != null) {
+ if (object != null) try {
for (final IdentifiedObject candidate : finder.find(object)) {
String c = toURN(candidate.getClass(),
getIdentifier(candidate, authority));
if (c == null && authority == null) {
@@ -606,6 +607,8 @@ public final class IdentifiedObjects extends Static {
urn = c;
}
}
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(FactoryException.class);
}
return urn;
}
@@ -642,7 +645,7 @@ public final class IdentifiedObjects extends Static {
@OptionalCandidate
public static Integer lookupEPSG(final IdentifiedObject object) throws
FactoryException {
Integer code = null;
- if (object != null) {
+ if (object != null) try {
for (final IdentifiedObject candidate :
newFinder(Constants.EPSG).find(object)) {
final Identifier id = getIdentifier(candidate, Citations.EPSG);
if (id != null) try {
@@ -655,6 +658,8 @@ public final class IdentifiedObjects extends Static {
warning("lookupEPSG", e);
}
}
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(FactoryException.class);
}
return code;
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
index a67a3e1d15..4b5e51a376 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
@@ -106,7 +106,7 @@ final class AuthorityFactoryIdentifier {
}
/**
- * The type of the factory needed.
+ * The type of the factory which is needed.
*/
final Type type;
@@ -123,7 +123,7 @@ final class AuthorityFactoryIdentifier {
private String authority;
/**
- * The version part of a URI, or {@code null} if none.
+ * The version part of a <abbr>URI</abbr>, or {@code null} if none.
* If the version contains alphabetic characters, they should be in lower
case.
*
* <h4>Example</h4>
@@ -139,6 +139,10 @@ final class AuthorityFactoryIdentifier {
* Creates a new identifier for a factory of the given type, authority and
version.
* The given authority shall be already in upper cases and the version in
lower cases
* (this is not verified by this constructor).
+ *
+ * @param type the type of the factory which is needed.
+ * @param authority the authority of the factory, in upper case.
+ * @param version the version part of a <abbr>URI</abbr>, or {@code
null} if none.
*/
private AuthorityFactoryIdentifier(final Type type, final String
authority, final String version) {
this.type = type;
@@ -164,6 +168,11 @@ final class AuthorityFactoryIdentifier {
/**
* Creates a new identifier for a factory of the given type, authority and
version.
* Only the version can be null.
+ *
+ * @param type the type of the factory which is needed.
+ * @param authority the authority of the factory, case-insensitive.
+ * @param version the version part of a <abbr>URI</abbr>, or {@code
null} if none.
+ * @return identifier for a factory of the given type, authority and
version.
*/
static AuthorityFactoryIdentifier create(final Type type, final String
authority, final String version) {
return new AuthorityFactoryIdentifier(type,
authority.toUpperCase(IDENTIFIER_LOCALE),
@@ -173,6 +182,9 @@ final class AuthorityFactoryIdentifier {
/**
* Returns an identifier for a factory of the same type as this identifier,
* but a different authority and no version.
+ *
+ * @param newAuthority the authority of the factory, case-insensitive.
+ * @return identifier for a factory of the given authority.
*/
AuthorityFactoryIdentifier unversioned(final String newAuthority) {
if (version == null && newAuthority.equals(authority)) {
@@ -201,6 +213,9 @@ final class AuthorityFactoryIdentifier {
/**
* Creates a new identifier for the same authority and version than this
identifier, but a different factory.
+ *
+ * @param type the type of the factory which is needed.
+ * @return identifier for a factory of the given type.
*/
AuthorityFactoryIdentifier newType(final Type newType) {
return new AuthorityFactoryIdentifier(newType, authority, version);
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index a5dfbcd4a5..6d950d6db6 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@ -16,9 +16,11 @@
*/
package org.apache.sis.referencing.factory;
+import java.util.Iterator;
import java.util.Set;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.opengis.util.FactoryException;
@@ -27,10 +29,12 @@ import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.datum.Datum;
-import org.apache.sis.referencing.AbstractIdentifiedObject;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.datum.DatumOrEnsemble;
+import org.apache.sis.referencing.privy.FilteredIterator;
+import org.apache.sis.referencing.privy.LazySet;
import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Disposable;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.collection.BackingStoreException;
@@ -43,7 +47,7 @@ import org.opengis.referencing.datum.DatumEnsemble;
/**
* Searches in an authority factory for objects approximately equal to a given
object.
- * This class can be used for fetching a fully defined {@linkplain
AbstractIdentifiedObject identified object}
+ * This class can be used for fetching a fully defined {@linkplain
IdentifiedObject identified object}
* from an incomplete one, for example from an object without "{@code ID[…]}"
or "{@code AUTHORITY[…]}"
* element in <i>Well Known Text</i>.
*
@@ -70,7 +74,7 @@ import org.opengis.referencing.datum.DatumEnsemble;
*/
public class IdentifiedObjectFinder {
/**
- * The domain of the search (for example whether to include deprecated
objects in the search).
+ * The domain of the search (for example, whether to include deprecated
objects in the search).
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.2
@@ -81,53 +85,56 @@ public class IdentifiedObjectFinder {
*/
public enum Domain {
/**
- * Fast lookup based only on embedded identifiers and names. If those
identification information
- * does not allow to locate an object in the factory, then the search
will return an empty set.
+ * Fast lookup based only on declared identifiers.
+ * If those identification information does not allow to locate an
object in the factory,
+ * then the {@code find(…)} method will return an empty set instead of
performing
+ * an exhaustive search in the geodetic dataset.
*
* <h4>Example</h4>
- * If {@link IdentifiedObjectFinder#find(IdentifiedObject)} is invoked
with an object having the {@code "4326"}
- * {@linkplain AbstractIdentifiedObject#getIdentifiers() identifier},
then the {@code find(…)} method will invoke
+ * If {@link #find(IdentifiedObject)} is invoked with an object having
the {@code "4326"}
+ * {@linkplain IdentifiedObject#getIdentifiers() identifier}, then the
{@code find(…)} method will invoke
* <code>factory.{@linkplain
GeodeticAuthorityFactory#createGeographicCRS(String)
createGeographicCRS}("4326")</code>
- * and compare the object from the factory with the object to search.
- * If the objects do not match, then another attempt will be done
using the
- * {@linkplain AbstractIdentifiedObject#getName() object name}. If
using name does not work neither,
+ * and compare the result with the object to search.
+ * If the two objects do not match, then some implementations may
perform another attempt using the
+ * {@linkplain IdentifiedObject#getName() object name}. If using the
name does not work neither,
* then {@code find(…)} method makes no other attempt and returns an
empty set.
*/
DECLARATION,
/**
- * Lookup based on valid (non-deprecated) objects known to the factory.
- * First, a fast lookup is performed based on {@link #DECLARATION}.
- * If the fast lookup gave no result, then a more extensive search is
performed by scanning the content
- * of the dataset.
+ * Lookup based on declared identifiers and on non-deprecated objects
known to the factory.
+ * First, a fast lookup is performed as described in {@link
#DECLARATION}.
+ * If the last lookup found some matches, those matches are returned
without scanning the rest of the database.
+ * It may be an incomplete set compared to what {@link
#EXHAUSTIVE_VALID_DATASET} would have returned.
+ * If the fast lookup gave no result, only then an exhaustive search
is performed by scanning
+ * the content of the geodetic dataset.
*
- * <h4>Example</h4>
- * If {@link IdentifiedObjectFinder#find(IdentifiedObject)} is invoked
with an object equivalent to the
- * {@linkplain org.apache.sis.referencing.CommonCRS#WGS84 WGS84}
geographic CRS but does not declare the
- * {@code "4326"} identifier and does not have the <q>WGS 84</q> name,
then the search based on
- * {@link #DECLARATION} will give no result. The {@code find(…)}
method will then scan the dataset for
- * geographic CRS using equivalent datum and coordinate system. This
may be a costly operation.
+ * <p>This is the default domain of {@link IdentifiedObjectFinder}.</p>
*
- * This is the default domain of {@link IdentifiedObjectFinder}.
+ * <h4>Example</h4>
+ * If {@link #find(IdentifiedObject)} is invoked with an object
equivalent to the
+ * {@linkplain org.apache.sis.referencing.CommonCRS#WGS84 WGS84}
geographic <abbr>CRS</abbr>
+ * but without declaring the {@code "4326"} identifier and without the
<q>WGS 84</q> name,
+ * then the initial lookup described in {@link #DECLARATION} will give
no result.
+ * As a fallback, the {@code find(…)} method scans the geodetic
dataset in search
+ * for geographic <abbr>CRS</abbr> equivalent to the specified object.
+ * It may be a costly operation.
*/
VALID_DATASET,
/**
- * Lookup unconditionally based on all valid (non-deprecated) objects
known to the factory.
+ * Search unconditionally based on all valid (non-deprecated) objects
known to the factory.
* This is similar to {@link #VALID_DATASET} except that the fast
{@link #DECLARATION} lookup is skipped.
- * Instead, a potentially costly scan of the database is
unconditionally performed
+ * Instead, a potentially costly scan of the geodetic dataset is
unconditionally performed
* (unless the result is already in the cache).
*
- * <p>This domain can be useful when the search {@linkplain
#isIgnoringAxes() ignores axis order}.
- * If axis order is <em>not</em> ignored, then this domain usually has
no advantage over {@link #VALID_DATASET}
- * (unless the geodetic dataset contains duplicated entries) to
justify the performance cost.</p>
- *
* <h4>Use case</h4>
- * The EPSG database sometimes contains two definitions for almost
identical geographic CRS,
- * one with (<var>latitude</var>, <var>longitude</var>) axis order and
one with reverse order
- * (e.g. EPSG::4171 versus EPSG::7084). It is sometimes useful to know
all variants of a given CRS.
- * The {@link #VALID_DATASET} domain may not give a complete set
because the "fast lookup by identifier"
- * optimization may prevent {@link IdentifiedObjectFinder} to scan the
rest of the database.
+ * The <abbr>EPSG</abbr> geodetic dataset sometimes contains two
definitions for almost identical
+ * geographic <abbr>CRS</abbr>, one with (<var>latitude</var>,
<var>longitude</var>) axis order
+ * and one with reverse order (e.g. EPSG::4171 versus EPSG::7084). It
is sometimes useful to know
+ * all variants of a given <abbr>CRS</abbr> in a search {@linkplain
#isIgnoringAxes() ignoring axis order}.
+ * The {@link #VALID_DATASET} domain may not give a complete set
because the "fast lookup by identifiers"
+ * optimization may prevent {@link IdentifiedObjectFinder} to scan the
rest of the geodetic dataset.
* This {@code EXHAUSTIVE_VALID_DATASET} domain forces such scan.
*
* @since 1.2
@@ -142,12 +149,6 @@ public class IdentifiedObjectFinder {
ALL_DATASET
}
- /**
- * The criterion for determining if a candidate found by {@code
IdentifiedObjectFinder}
- * should be considered equals to the requested object.
- */
- static final ComparisonMode COMPARISON_MODE = ComparisonMode.APPROXIMATE;
-
/**
* The factory to use for creating objects. This is the factory specified
at construction time.
*/
@@ -183,7 +184,7 @@ public class IdentifiedObjectFinder {
/**
* Creates a finder using the specified factory.
*
- * <h4>API note</h4>
+ * <h4><abbr>API</abbr> note</h4>
* This constructor is protected because instances of this class should
not be created directly.
* Use {@link GeodeticAuthorityFactory#newIdentifiedObjectFinder()}
instead.
*
@@ -202,7 +203,7 @@ public class IdentifiedObjectFinder {
* An example of wrapper is {@link ConcurrentAuthorityFactory}'s finder.
*
* <p>This method also copies the configuration of the given finder, thus
providing a central place
- * where to add calls to setters methods if such methods are added in a
future SIS version.</p>
+ * where to add calls to setter methods if such methods are added in a
future <abbr>SIS</abbr> version.</p>
*
* @param other the cache or the adapter wrapping this finder.
*/
@@ -213,9 +214,10 @@ public class IdentifiedObjectFinder {
}
/**
- * Returns the domain of the search (for example whether to include
deprecated objects in the search).
- * If {@code DECLARATION}, only a fast lookup based on embedded
identifiers and names will be performed.
- * Otherwise an exhaustive full scan against all registered objects will
be performed (may be slow).
+ * Returns the domain of the search (for example, whether to include
deprecated objects in the search).
+ * If the domain is {@code DECLARATION}, then the {@code find(…)} method
will only perform a fast lookup
+ * based on the identifiers and the names of the object to search.
+ * Otherwise, an exhaustive scan of the geodetic dataset will be performed
(may be slow).
*
* <p>The default value is {@link Domain#VALID_DATASET}.</p>
*
@@ -226,8 +228,8 @@ public class IdentifiedObjectFinder {
}
/**
- * Sets the domain of the search (for example whether to include
deprecated objects in the search).
- * If this method is never invoked, then the default value is {@link
Domain#VALID_DATASET}.
+ * Sets the domain of the search (for example, whether to include
deprecated objects in the search).
+ * If this method is never invoked, the default value is {@link
Domain#VALID_DATASET}.
*
* @param domain the domain of the search.
*/
@@ -259,13 +261,33 @@ public class IdentifiedObjectFinder {
}
/**
- * Returns {@code true} if a candidate found by {@code
IdentifiedObjectFinder} should be considered equals to the
- * requested object. This method invokes the {@code equals(…)} method on
the {@code candidate} argument instead
- * than on the user-specified {@code object} on the assumption that
implementations coming from the factory are
- * more reliable than user-specified objects.
+ * Returns the comparison mode to use when comparing a candidate against
the object to search.
+ */
+ private ComparisonMode getComparisonMode() {
+ return ignoreAxes ? ComparisonMode.ALLOW_VARIANT :
ComparisonMode.APPROXIMATE;
+ }
+
+ /**
+ * Returns {@code true} if a candidate created by a factory should be
considered equal to the object to search.
+ * The {@code mode} and {@code proxy} arguments may be snapshots of the
{@code IdentifiedObjectFinder}'s state
+ * taken at the time when the {@link Instances} iterable has been created.
+ *
+ * <h4>Implementation note</h4>
+ * This method invokes the {@code equals(…)} method on the {@code
candidate} argument instead of {@code object}
+ * specified by the user on the assumption that implementations coming
from the factory are more reliable than
+ * user-specified objects.
+ *
+ * @param candidate an object created by an authority factory.
+ * @param object the user-specified object to search.
+ * @param mode value of {@link #getComparisonMode()} (may be a
snapshot).
+ * @param proxy value of {@link #proxy} (may be a snapshot).
+ * @return whether the given candidate can be considered equal to the
object to search.
+ *
+ * @see #createAndFilter(AuthorityFactory, String, IdentifiedObject)
*/
- private boolean match(final IdentifiedObject candidate, final
IdentifiedObject object) {
- final ComparisonMode mode = ignoreAxes ? ComparisonMode.ALLOW_VARIANT
: COMPARISON_MODE;
+ private static boolean match(final IdentifiedObject candidate, final
IdentifiedObject object,
+ final ComparisonMode mode, final
AuthorityFactoryProxy<?> proxy)
+ {
if (Utilities.deepEquals(candidate, object, mode)) {
return true;
}
@@ -302,53 +324,6 @@ public class IdentifiedObjectFinder {
return result;
}
- /**
- * Lookups objects which are approximately equal to the specified object.
- * This method tries to instantiate objects identified by the {@linkplain
#getCodeCandidates set of candidate codes}
- * with the authority factory specified at construction time.
- * The created objects which are equal to the specified object in the
- * the sense of {@link ComparisonMode#APPROXIMATE} are returned.
- *
- * @param object the object looked up.
- * @return the identified objects, or an empty set if not found.
- * @throws FactoryException if an error occurred while creating an object.
- */
- public Set<IdentifiedObject> find(final IdentifiedObject object) throws
FactoryException {
- Set<IdentifiedObject> result =
getFromCache(Objects.requireNonNull(object));
- if (result == null) {
- final AuthorityFactoryProxy<?> previousProxy = proxy;
- proxy = AuthorityFactoryProxy.getInstance(object.getClass());
- try {
- if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
- /*
- * First check if one of the identifiers can be used to
find directly an identified object.
- * Verify that the object that we found is actually equal
to given one; we do not blindly
- * trust the identifiers in the user object.
- */
- IdentifiedObject candidate = createFromIdentifiers(object);
- if (candidate != null) {
- result = Set.of(candidate);
- }
- }
- /*
- * Here we exhausted the quick paths.
- * Perform a full scan (costly) if we are allowed to,
otherwise abandon.
- */
- if (result == null) {
- if (domain == Domain.DECLARATION) {
- result = Set.of();
- } else {
- result = createFromCodes(object);
- }
- }
- } finally {
- proxy = previousProxy;
- }
- result = cache(object, result); // Costly operations (even if
the result is empty) are worth to cache.
- }
- return result;
- }
-
/**
* Lookups only one object which is approximately equal to the specified
object.
* This method invokes {@link #find(IdentifiedObject)}, then examine the
returned {@code Set} as below:
@@ -364,7 +339,7 @@ public class IdentifiedObjectFinder {
*
* @param object the object looked up.
* @return the identified object, or {@code null} if none or ambiguous.
- * @throws FactoryException if an error occurred while creating an object.
+ * @throws FactoryException if an error occurred while fetching the
authority code candidates.
*/
public IdentifiedObject findSingleton(final IdentifiedObject object)
throws FactoryException {
/*
@@ -376,15 +351,15 @@ public class IdentifiedObjectFinder {
boolean ambiguous = false;
try {
for (final IdentifiedObject candidate : find(object)) {
- final boolean equalsIncludingAxes = !ignoreAxes ||
Utilities.deepEquals(candidate, object, COMPARISON_MODE);
+ boolean matchAxes = !ignoreAxes ||
Utilities.deepEquals(candidate, object, ComparisonMode.APPROXIMATE);
if (result != null) {
ambiguous = true;
- if (sameAxisOrder & equalsIncludingAxes) {
+ if (sameAxisOrder & matchAxes) {
return null; // Found two matches even when
taking in account axis order.
}
}
result = candidate;
- sameAxisOrder = equalsIncludingAxes;
+ sameAxisOrder = matchAxes;
}
} catch (BackingStoreException e) {
throw e.unwrapOrRethrow(FactoryException.class);
@@ -393,110 +368,177 @@ public class IdentifiedObjectFinder {
}
/**
- * Creates an object equals (optionally ignoring metadata), to the
specified object
- * using only the {@linkplain AbstractIdentifiedObject#getIdentifiers
identifiers}.
- * If no such object is found, returns {@code null}.
+ * Lookups objects which are approximately equal to the specified object.
+ * This method tries to instantiate objects identified by the {@linkplain
#getCodeCandidates set of candidate codes}
+ * using the {@linkplain #factory authority factory} specified at
construction time.
+ * {@link FactoryException}s thrown during object creations are logged and
otherwise ignored.
+ * The successfully created objects which are equal to the specified
object in the sense of
+ * {@link ComparisonMode#APPROXIMATE} or {@link
ComparisonMode#ALLOW_VARIANT ALLOW_VARIANT}
+ * (depending on whether {@linkplain #isIgnoringAxes() axes are ignored})
are included in the returned set.
*
- * <p>This method may be used in order to get a fully identified object
from a partially identified one.</p>
+ * <h4>Exception handling</h4>
+ * This method may return a lazy set, in which case some checked
exceptions may occur at iteration time.
+ * These exceptions are wrapped in a {@link BackingStoreException}.
*
* @param object the object looked up.
- * @return the identified object, or {@code null} if not found.
- * @throws FactoryException if an error occurred while creating an object.
- *
- * @see #createFromCodes(IdentifiedObject)
+ * @return the identified objects, or an empty set if not found.
+ * @throws FactoryException if an error occurred while fetching the
authority code candidates.
*/
- private IdentifiedObject createFromIdentifiers(final IdentifiedObject
object) throws FactoryException {
- for (final Identifier id : object.getIdentifiers()) {
- final String code = IdentifiedObjects.toString(id);
- /*
- * We will process only codes with a namespace (e.g.
"AUTHORITY:CODE") for avoiding ambiguity.
- * We do not try to check by ourselves if the identifier is in the
namespace of the factory,
- * because calling factory.getAuthorityCodes() or
factory.getCodeSpaces() may be costly for
- * some implementations.
- */
- if (code.indexOf(Constants.DEFAULT_SEPARATOR) >= 0) {
- final IdentifiedObject candidate;
- try {
- candidate = create(code);
- } catch (NoSuchAuthorityCodeException e) {
- // The identifier was not recognized. No problem, let's go
on.
- exceptionOccurred(e);
- continue;
- }
- if (match(candidate, object)) {
- return candidate;
- }
+ public Set<IdentifiedObject> find(final IdentifiedObject object) throws
FactoryException {
+ Set<IdentifiedObject> result =
getFromCache(Objects.requireNonNull(object));
+ if (result == null) try {
+ final AuthorityFactoryProxy<?> previousProxy = proxy;
+ proxy = AuthorityFactoryProxy.getInstance(object.getClass());
+ try {
+ result = createFromCodes(object);
+ } finally {
+ proxy = previousProxy;
}
+ result = cache(object, result); // Costly operations (even if
the result is empty) are worth to cache.
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(FactoryException.class);
}
- return null;
+ return result;
}
/**
- * Creates an object equals (optionally ignoring metadata), to the
specified object.
- * This method scans the {@linkplain #getCodeCandidates(IdentifiedObject)
authority codes},
- * creates the objects and returns the first one which is equal to the
specified object in
- * the sense of {@link Utilities#deepEquals(Object, Object,
ComparisonMode)}.
+ * Creates objects approximately equal to the specified object by
iterating over authority code candidates.
+ * This method is invoked by {@link #find(IdentifiedObject)} after it has
been verified that the result is not in the cache.
+ * The default implementation iterates over the {@linkplain
#getCodeCandidates(IdentifiedObject) authority code candidates},
+ * creates the objects and returns the ones which are approximately equal
to the specified object.
*
- * <p>This method may be used in order to get a fully {@linkplain
AbstractIdentifiedObject identified object}
- * from an object without {@linkplain
AbstractIdentifiedObject#getIdentifiers() identifiers}.</p>
+ * <h4>Exception handling</h4>
+ * This method may return a lazy set, in which case some checked
exceptions may occur at iteration time.
+ * These exceptions are wrapped in a {@link BackingStoreException}.
*
* @param object the object looked up.
- * @return the identified object, or {@code null} if not found.
- * @throws FactoryException if an error occurred while scanning through
authority codes.
- *
- * @see #createFromIdentifiers(IdentifiedObject)
+ * @return the identified objects, or an empty set if not found.
+ * @throws FactoryException if an error occurred while fetching the
authority code candidates.
+ * @throws BackingStoreException allowed for convenience, will be
unwrapped by the caller.
*/
Set<IdentifiedObject> createFromCodes(final IdentifiedObject object)
throws FactoryException {
- final var result = new LinkedHashSet<IdentifiedObject>(); // We
need to preserve order.
- final boolean finer =
Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
- try {
- for (final String code : getCodeCandidates(object)) {
- final IdentifiedObject candidate;
- try {
- candidate = create(code);
- } catch (FactoryException e) {
- exceptionOccurred(e);
- continue;
- }
- if (match(candidate, object)) {
- result.add(candidate);
+ return new Instances(this, object);
+ }
+
+ /**
+ * The set of geodetic instances created from the code candidates.
+ * This is a lazy set, where each instance is created only when first
requested.
+ * Checked exceptions are wrapped in {@link BackingStoreException}.
+ *
+ * <h2>Implementation note</h2>
+ * This class should not keep a reference to the enclosing class (it is
static for that reason),
+ * because some subclasses of {@link IdentifiedObjectFinder} are
short-lived data access objects
+ * holding resources such as database connections.
+ */
+ private static final class Instances extends LazySet<IdentifiedObject>
implements Function<String, IdentifiedObject> {
+ /** Copy of {@link IdentifiedObjectFinder#factory}. */
+ private final AuthorityFactory factory;
+
+ /** Snapshot of {@link IdentifiedObjectFinder#proxy}. */
+ private final AuthorityFactoryProxy<?> proxy;
+
+ /** The authority codes form which to create object candidates. */
+ private final Iterable<String> codes;
+
+ /** The comparison mode for deciding if a candidate is a match. */
+ private final ComparisonMode mode;
+
+ /** Previously created objects for removing duplicates. */
+ private final Set<IdentifiedObject> existing;
+
+ /** The user-specified object to search. */
+ private final IdentifiedObject object;
+
+ /** Whether at least one match has been found. */
+ private boolean hasMatches;
+
+ /**
+ * Creates a new collection for the given object to search.
+ *
+ * @param factory value of {@link IdentifiedObjectFinder#factory}.
+ * @param object the user-specified object to search.
+ * @throws FactoryException if an error occurred while fetching the
authority code candidates.
+ */
+ Instances(final IdentifiedObjectFinder source, final IdentifiedObject
object) throws FactoryException {
+ factory = source.factory;
+ proxy = source.proxy;
+ codes = source.getCodeCandidates(object);
+ mode = source.getComparisonMode();
+ existing = new HashSet<>();
+ this.object = object;
+ }
+
+ /**
+ * Creates the iterator which will create objects from authority codes.
+ * This method will be invoked only when first needed and at most
once, unless {@link #reload()} is invoked.
+ *
+ * @return iterator over objects created from authority codes.
+ */
+ @Override
+ protected Iterator<IdentifiedObject> createSourceIterator() {
+ return new FilteredIterator<>(codes.iterator(), this);
+ }
+
+ /**
+ * Creates an object from the given code, and verifies if it is
considered equals to the object to search.
+ * This method is invoked by {@link FilteredIterator}. Checked
exceptions are logged. If the object cannot
+ * be created or is not approximately equal to the object to search,
then this method returns {@code true},
+ * which is understood by {@link FilteredIterator} as an instruction
to look for the next element.
+ *
+ * @param code one of the authority codes returned by {@link
#getCodeCandidates(IdentifiedObject)}.
+ * @return object created from the given code if it is approximately
equal to the object to search.
+ */
+ @Override
+ public IdentifiedObject apply(final String code) {
+ final boolean finer =
Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
+ try {
+ IdentifiedObject candidate = (IdentifiedObject)
proxy.createFromAPI(factory, code);
+ if (match(candidate, object, mode, proxy) &&
existing.add(candidate)) {
+ if (!hasMatches) {
+ hasMatches = true;
+ if (codes instanceof Disposable) {
+ ((Disposable) codes).dispose(); // For stopping
iteration after the easy matches.
+ }
+ }
+ return candidate;
}
+ } catch (FactoryException e) {
+ exceptionOccurred(e);
+ } finally {
+ Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS,
finer);
}
- } catch (BackingStoreException e) {
- throw e.unwrapOrRethrow(FactoryException.class);
- } finally {
- Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS,
finer);
+ return null;
}
- return result;
}
/**
- * Creates an object for the given identifier, name or alias. This method
is invoked by the default
- * {@link #find(IdentifiedObject)} method implementation with the
following argument values, in order
- * (from less expensive to most expensive search operation):
- *
- * <ol>
- * <li>All {@linkplain AbstractIdentifiedObject#getIdentifier()
identifiers} of the object to search,
- * formatted in an {@linkplain
IdentifiedObjects#toString(Identifier) "AUTHORITY:CODE"} pattern.</li>
- * <li>The {@linkplain AbstractIdentifiedObject#getName() name} of the
object to search,
- * {@linkplain org.apache.sis.referencing.NamedIdentifier#getCode()
without authority}.</li>
- * <li>All {@linkplain AbstractIdentifiedObject#getAlias() aliases} of
the object to search.</li>
- * <li>Each code returned by the {@link
#getCodeCandidates(IdentifiedObject)} method, in iteration order.</li>
- * </ol>
- *
- * @param code the authority code for which to create an object.
- * @return the identified object for the given code, or {@code null} to
stop attempts.
+ * Creates an object from the given code, and verifies if it is considered
equals to the object to search.
+ * This is a helper method for subclasses. This method does the same work
as {@link Instances}, but allows
+ * the subclass to specify an alternative authority factory.
+ *
+ * @param factory the authority factory to use. This is not necessarily
{@link #factory}.
+ * @param code the authority code for which to create an object
candidate.
+ * @param object the user-specified object to search.
+ * @return instance for the given code, or {@code null} if not
approximately equal to the object to search.
* @throws NoSuchAuthorityCodeException if no object is found for the
given code. It may happen if {@code code}
* was a name or alias instead of an identifier and the factory
supports only search by identifier.
* @throws FactoryException if an error occurred while creating the object.
*/
- private IdentifiedObject create(final String code) throws FactoryException
{
- return (IdentifiedObject) proxy.createFromAPI(factory, code);
+ final IdentifiedObject createAndFilter(final AuthorityFactory factory,
final String code, final IdentifiedObject object)
+ throws FactoryException
+ {
+ final boolean finer =
Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
+ try {
+ final var candidate = (IdentifiedObject)
proxy.createFromAPI(factory, code);
+ return match(candidate, object, getComparisonMode(), proxy) ?
candidate : null;
+ } finally {
+ Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS,
finer);
+ }
}
/**
* Returns a set of authority codes that <em>may</em> identify the same
object as the specified one.
- * The elements may be determined from object identifiers, from object
names, or from a more extensive search in the database.
+ * The codes may be determined from object identifiers, names, aliases or
extensive search in the geodetic dataset.
* The effort in populating the returned set is specified by the
{@linkplain #getSearchDomain() search domain}.
* The returned set should contain at least the codes of every objects in
the search domain
* that are {@linkplain ComparisonMode#APPROXIMATE approximately equal} to
the specified object.
@@ -513,23 +555,167 @@ public class IdentifiedObjectFinder {
* The exception cause is often the checked {@link FactoryException}.
*
* <h4>Default implementation</h4>
- * The default implementation returns the same set as
- * <code>{@linkplain GeodeticAuthorityFactory#getAuthorityCodes(Class)
getAuthorityCodes}(type)</code>
- * where {@code type} is the interface specified at construction type.
+ * The default implementation returns codes defined from {@code
object.getIdentifiers()},
+ * or {@code factory.getAuthorityCodes(type)} where {@code type} is
derived from {@code object} class,
+ * or a combination of both collection, depending on the {@linkplain
#getSearchDomain() search domain}.
* Subclasses should override this method in order to return a smaller
set, if they can.
*
* @param object the object looked up.
* @return a set of code candidates.
* @throws FactoryException if an error occurred while fetching the set of
code candidates.
+ *
+ * @see IdentifiedObject#getIdentifiers()
+ * @see AuthorityFactory#getAuthorityCodes(Class)
*/
protected Iterable<String> getCodeCandidates(final IdentifiedObject
object) throws FactoryException {
- return
factory.getAuthorityCodes(proxy.type.asSubclass(IdentifiedObject.class));
+ /*
+ * Undocumented contract: if the Iterable implements `Dispose`, its
method will be invoked
+ * after the first match has been found. This is interpreted as a hint
that the iteration
+ * can stop earlier than it would normally do.
+ */
+ final Class<? extends IdentifiedObject> type =
proxy.type.asSubclass(IdentifiedObject.class);
+ if (domain == Domain.EXHAUSTIVE_VALID_DATASET) {
+ return factory.getAuthorityCodes(type);
+ }
+ final boolean easy = (domain == Domain.DECLARATION);
+ final Set<Identifier> identifiers = object.getIdentifiers();
+ if (identifiers.isEmpty()) {
+ return easy ? Set.of() : factory.getAuthorityCodes(type);
+ }
+ return new Codes(factory, identifiers, easy ? null : type);
+ }
+
+ /**
+ * Union of identifier codes followed by code candidates fetched from the
geodetic dataset.
+ * The codes returned by this iterable are, in this order:
+ *
+ * <ol>
+ * <li>{@link IdentifiedObject#getIdentifiers()} (filtered with {@link
#apply(Identifier)})</li>
+ * <li>{@link AuthorityFactory#getAuthorityCodes(Class)} (skipped if the
class is null)</li>
+ * </ol>
+ *
+ * <h2>Implementation note</h2>
+ * This class should not keep a reference to the enclosing class (it is
static for that reason),
+ * because some subclasses of {@link IdentifiedObjectFinder} are
short-lived data access objects
+ * holding resources such as database connections.
+ */
+ private static final class Codes implements Iterable<String>,
Function<Identifier, String>, Disposable {
+ /** Copy of {@link IdentifiedObjectFinder#factory}. */
+ private final AuthorityFactory factory;
+
+ /** The identifiers of the object to search. */
+ private final Set<Identifier> identifiers;
+
+ /** Type of objects to request for code candidates, or {@code null}
for not requesting code candidates. */
+ private Class<? extends IdentifiedObject> type;
+
+ /** Code candidates, created when first needed. This collection may be
costly to create and/or to use. */
+ private Iterable<String> codes;
+
+ /**
+ * Creates a new union of identifier codes and candidate codes.
+ *
+ * @param factory value of {@link IdentifiedObjectFinder#factory}.
+ * @param identifiers identifiers of the object to search.
+ * @param type type of objects to request for code candidates,
or {@code null}.
+ */
+ Codes(final AuthorityFactory factory, final Set<Identifier>
identifiers, final Class<? extends IdentifiedObject> type) {
+ this.factory = factory;
+ this.identifiers = identifiers;
+ this.type = type;
+ }
+
+ /**
+ * Invoked when the caller requested to stop the iteration after the
current group of elements.
+ * A group of elements is either the codes specified by the
identifiers, or the codes found in
+ * the database. We will avoid to stop in the middle of a group.
+ *
+ * <p>This is an undocumented feature of {@link
#createFromCodes(IdentifiedObject)}
+ * for stopping an iteration early when at least one match has been
found.</p>
+ */
+ @Override
+ public void dispose() {
+ type = null;
+ }
+
+ /**
+ * Converts the given identifier to a code returned by {@link
#getIdentifiers()}.
+ * We accept only codes with a namespace (e.g. "AUTHORITY:CODE") for
avoiding ambiguity.
+ * We do not try to check by ourselves if the identifier is in the
namespace of the factory,
+ * because calling {@code factory.getAuthorityCodes()} or {@code
factory.getCodeSpaces()}
+ * may be costly for some implementations.
+ */
+ @Override
+ public String apply(final Identifier id) {
+ final String code = IdentifiedObjects.toString(id);
+ return (code.indexOf(Constants.DEFAULT_SEPARATOR) >= 0) ? code :
null;
+ }
+
+ /**
+ * Returns an iterator over codes of the identifiers of the object to
search.
+ * The iteration does not include the {@code Identifier} of the name
because, at least
+ * in Apache <abbr>SIS</abbr> implementations, the factories that
accept object names
+ * already override {@link #getCodeCandidates(IdentifiedObject)} for
including names.
+ */
+ final Iterator<String> getIdentifiers() {
+ return new FilteredIterator<>(identifiers.iterator(), this);
+ }
+
+ /**
+ * Returns an iterator over the code candidates. This method should be
invoked only in last resort.
+ *
+ * @throws BackingStoreException if an error occurred while fetching
the collection of authority codes.
+ */
+ final Iterator<String> getAuthorityCodes() {
+ if (codes == null) {
+ if (type == null) {
+ codes = Set.of();
+ } else try {
+ codes = factory.getAuthorityCodes(type);
+ } catch (FactoryException e) {
+ throw new BackingStoreException(e);
+ }
+ }
+ return codes.iterator();
+ }
+
+ /**
+ * Returns an iterator over the identifiers followed by the code
candidates.
+ */
+ @Override
+ public Iterator<String> iterator() {
+ return new Iterator<String>() {
+ /** The iterator of the code to return. */
+ private Iterator<String> codes = getIdentifiers();
+
+ /** Whether {@link #codes} is for code candidates. */
+ private boolean isCodeCandidates;
+
+ /** Returns whether there is more codes to return. */
+ @Override public boolean hasNext() {
+ if (!isCodeCandidates) {
+ if (codes.hasNext()) return true;
+ codes = getAuthorityCodes();
+ isCodeCandidates = true;
+ }
+ return codes.hasNext();
+ }
+
+ /** Returns the next code. */
+ @Override public String next() {
+ if (!(isCodeCandidates || codes.hasNext())) {
+ codes = getAuthorityCodes();
+ }
+ return codes.next();
+ }
+ };
+ }
}
/**
* Invoked when an exception occurred during the creation of a candidate
from a code.
*/
- private static void exceptionOccurred(final FactoryException exception) {
+ static void exceptionOccurred(final FactoryException exception) {
if (GeodeticAuthorityFactory.LOGGER.isLoggable(Level.FINER)) {
/*
* use `getMessage()` instead of `getLocalizedMessage()` for
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
index 9e819db570..1c049568bb 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
@@ -42,6 +42,7 @@ import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
+import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.extent.Extent;
import org.opengis.parameter.ParameterDescriptor;
@@ -857,7 +858,7 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
* rigorous approach in a future SIS version.
*/
if (parameters != null || code.indexOf(CommonAuthorityCode.SEPARATOR)
>= 0) {
- final StringBuilder buffer = new StringBuilder(authority.length()
+ code.length() + 1)
+ final var buffer = new StringBuilder(authority.length() +
code.length() + 1)
.append(authority).append(Constants.DEFAULT_SEPARATOR).append(code);
if (parameters != null) {
for (final String p : parameters) {
@@ -1738,12 +1739,12 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
* A {@link IdentifiedObjectFinder} which tests every factories declared
in the
* {@linkplain MultiAuthoritiesFactory#getAllFactories() collection of
factories}.
*/
- private static class Finder extends IdentifiedObjectFinder {
+ private static final class Finder extends IdentifiedObjectFinder {
/**
* The finders of all factories, or {@code null} if not yet fetched.
* We will create this array only when first needed in order to avoid
instantiating the factories
- * before needed (for example we may be able to find an object using
only its code). However if we
- * need to create this array, then we will create it fully (for all
factories at once).
+ * before needed (for example we may be able to find an object using
only its code). However,
+ * if we need to create this array, then we will create it fully (for
all factories at once).
*/
private IdentifiedObjectFinder[] finders;
@@ -1781,18 +1782,77 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
}
/**
- * Delegates to every factories registered in the enclosing {@link
MultiAuthoritiesFactory},
- * in iteration order. This method is invoked only if the parent class
failed to find the
- * object by its identifiers and by its name. At this point, as a last
resort, we will scan
- * over the objects in the database.
+ * Creates objects equal (optionally ignoring metadata) to the
specified object using the given identifiers.
+ * This method may be used in order to get a fully identified object
from a partially identified one.
+ *
+ * @param type the type of the factory which is needed.
+ * @param object the user-specified object to search.
+ * @param identifiers {@code object.getIdentifiers()} or {@code
object.getName()}.
+ * @param result the collection where to add the object
instantiated from the identifiers.
+ * @throws FactoryException if an error occurred while creating an
object.
+ */
+ private void createFromIdentifiers(final
AuthorityFactoryIdentifier.Type type, final IdentifiedObject object,
+ final Iterable<Identifier> identifiers, final
Set<IdentifiedObject> result) throws FactoryException
+ {
+ for (final Identifier identifier : identifiers) {
+ final String authority = identifier.getCodeSpace();
+ if (authority != null) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ final var factory = (MultiAuthoritiesFactory) this.factory;
+ final IdentifiedObject candidate;
+ try {
+ final var fid =
AuthorityFactoryIdentifier.create(type, authority, identifier.getVersion());
+ candidate =
createAndFilter(factory.getAuthorityFactory(fid), identifier.getCode(), object);
+ } catch (NoSuchAuthorityCodeException e) {
+ // The identifier was not recognized. No problem,
let's go on.
+ exceptionOccurred(e);
+ continue;
+ }
+ if (candidate != null) {
+ result.add(candidate);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates objects approximately equal to the specified object by
iterating over authority code candidates.
+ * This method is invoked by {@link #find(IdentifiedObject)} when the
result was not already in the cache.
+ * First, this method tries to delegate to the factory specified by
the name space of each identifier.
+ * If this quick search using declared identifiers did not worked,
then this method delegates to every
+ * factories registered in the enclosing {@link
MultiAuthoritiesFactory}, in iteration order.
*
* <p>This method shall <strong>not</strong> delegate the job to the
parent class, as the default
* implementation in the parent class is very inefficient. We need to
delegate to the finders of
* all factories, so we can leverage their potentially more efficient
algorithms.</p>
+ *
+ * @param object the object looked up.
+ * @return the identified objects, or an empty set if not found.
+ * @throws FactoryException if an error occurred while fetching the
authority code candidates.
+ * @throws BackingStoreException allowed for convenience, will be
unwrapped by the caller.
*/
@Override
final Set<IdentifiedObject> createFromCodes(final IdentifiedObject
object) throws FactoryException {
- if (finders == null) try {
+ if (getSearchDomain() != Domain.EXHAUSTIVE_VALID_DATASET) {
+ for (AuthorityFactoryIdentifier.Type type :
AuthorityFactoryIdentifier.Type.values()) {
+ if (type.api.isInstance(object)) {
+ final var result = new
LinkedHashSet<IdentifiedObject>();
+ createFromIdentifiers(type, object,
object.getIdentifiers(), result);
+ if (result.isEmpty()) {
+ createFromIdentifiers(type, object,
CollectionsExt.singletonOrEmpty(object.getName()), result);
+ if (result.isEmpty()) {
+ break;
+ }
+ }
+ return result;
+ }
+ }
+ }
+ /*
+ * No object created from the identifiers or the name.
+ * Prepare finders for each factory in iteration order.
+ */
+ if (finders == null) {
final var list = new ArrayList<IdentifiedObjectFinder>();
final var unique = new
IdentityHashMap<AuthorityFactory,Boolean>();
final Iterator<AuthorityFactory> it =
((MultiAuthoritiesFactory) factory).getAllFactories();
@@ -1801,21 +1861,35 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
if (candidate instanceof GeodeticAuthorityFactory &&
unique.put(candidate, Boolean.TRUE) == null) {
IdentifiedObjectFinder finder =
((GeodeticAuthorityFactory) candidate).newIdentifiedObjectFinder();
if (finder != null) { // Should never be null
according method contract, but we are paranoiac.
-
finder.setSearchDomain(Domain.EXHAUSTIVE_VALID_DATASET);
- finder.setWrapper(this);
list.add(finder);
}
}
}
finders = list.toArray(IdentifiedObjectFinder[]::new);
- } catch (BackingStoreException e) {
- throw e.unwrapOrRethrow(FactoryException.class);
}
- final var found = new LinkedHashSet<IdentifiedObject>();
+ /*
+ * If only one finder returns a non-empty set, we return that set
without copying
+ * the elements in a `LinkedHashSet` because it may a lazy set. We
merge the sets
+ * only if really necessary.
+ */
+ Set<IdentifiedObject> merged = null, result = null;
for (final IdentifiedObjectFinder finder : finders) {
- found.addAll(finder.find(object));
+ finder.setWrapper(this); // Also copy the configuration of
this finder.
+ final Set<IdentifiedObject> codes = finder.find(object);
+ if (!codes.isEmpty()) {
+ if (result == null) {
+ result = codes;
+ } else {
+ if (merged == null) {
+ merged = new LinkedHashSet<>(result);
+ }
+ if (merged.addAll(codes)) {
+ result = merged;
+ }
+ }
+ }
}
- return found;
+ return (result != null) ? result : Set.of();
}
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index 2b51586929..d7a3d64524 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -44,6 +44,7 @@ import org.opengis.referencing.datum.TemporalDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.EngineeringDatum;
import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Disposable;
import org.apache.sis.util.Exceptions;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
@@ -75,6 +76,10 @@ import org.opengis.referencing.crs.DerivedCRS;
* An implementation of {@link IdentifiedObjectFinder} which scans over a
smaller set of authority codes.
* This is used for finding the EPSG code of a given Coordinate Reference
System or other geodetic object.
*
+ * <h4>Lifetime</h4>
+ * The finder returned by this method depends on the {@link EPSGDataAccess}
instance given to the constructor.
+ * The finder should not be used after this factory has been closed or given
back to the {@link EPSGFactory}.
+ *
* @author Martin Desruisseaux (IRD, Geomatys)
*/
final class EPSGCodeFinder extends IdentifiedObjectFinder {
@@ -152,7 +157,7 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
private <T extends IdentifiedObject> Condition dependencies(final String
column,
final Class<T> type, final T dependency, final boolean ignoreAxes)
throws FactoryException
{
- if (dependency != null) {
+ if (dependency != null) try {
final Class<? extends IdentifiedObject> pt = declaredType;
final boolean previous = isIgnoringAxes();
final Set<IdentifiedObject> find;
@@ -176,6 +181,8 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
if (!filters.isEmpty()) {
return new Condition(column, filters);
}
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(FactoryException.class);
}
return null;
}
@@ -594,7 +601,7 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
@Override
protected Iterable<String> getCodeCandidates(final IdentifiedObject
object) throws FactoryException {
for (final TableInfo source : TableInfo.values()) {
- if (source.isCandidate() && source.type.isInstance(object)) try {
+ if (source.isSpecificEnough() && source.type.isInstance(object))
try {
return new CodeCandidates(object, source);
} catch (SQLException exception) {
throw databaseFailure(exception);
@@ -606,8 +613,13 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
/**
* Set of authority codes that <strong>may</strong> identify the same
object as the specified one.
* This collection returns the codes that can be obtained easily before
the more expensive searches.
+ *
+ * @todo We should not keep a reference to the enclosing finder, because
the {@link EPSGDataAccess}
+ * may become invalid before the iteration is completed. For now, this is
not a problem because this
+ * collection is copied by the {@link EPSGFactory} finder. But this is
suboptimal because it defeats
+ * the purpose of object lazy instantiation.
*/
- private final class CodeCandidates implements Iterable<String> {
+ private final class CodeCandidates implements Iterable<String>, Disposable
{
/** The object to search. */
private final IdentifiedObject object;
@@ -661,6 +673,19 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
}
}
+ /**
+ * Invoked when the caller requested to stop the iteration after the
current group of elements.
+ * A group of elements is either the codes specified by the
identifiers, or the codes found in
+ * the database. We will avoid to stop in the middle of a group.
+ *
+ * <p>This is an undocumented feature of {@link
#createFromCodes(IdentifiedObject)}
+ * for stopping an iteration early when at least one match has been
found.</p>
+ */
+ @Override
+ public void dispose() {
+ searchMethod = 3; // The value after the last switch in
`fetchMoreCodes(Collection)`.
+ }
+
/**
* Populates the given collection with code candidates.
* This method tries less expansive search methods before to tries
more expensive search methods.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index d39afd31b2..f3e64af99e 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -601,7 +601,7 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
}
if (source == null) {
for (TableInfo c : TableInfo.values()) {
- if (c.isCandidate() && c.type.isAssignableFrom(type)) {
+ if (c.isSpecificEnough() && c.type.isAssignableFrom(type)) {
if (source != null) {
return null; // The specified type is too
generic.
}
@@ -1436,7 +1436,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
try {
final int key = isPrimaryKey ? toPrimaryKeys(null, code)[0] : 0;
for (final TableInfo source : TableInfo.values()) {
- if (!(source.isCandidate() &&
IdentifiedObject.class.isAssignableFrom(source.type))) {
+ if (!(source.isSpecificEnough() &&
IdentifiedObject.class.isAssignableFrom(source.type))) {
continue;
}
final String column = isPrimaryKey ? source.codeColumn :
source.nameColumn;
@@ -3432,6 +3432,11 @@ next: while (r.next()) {
* The finder tries to fetch a fully {@linkplain AbstractIdentifiedObject
identified object} from an incomplete one,
* for example from an object without "{@code ID[…]}" or "{@code
AUTHORITY[…]}" element in <i>Well Known Text</i>.
*
+ * <h4>Lifetime</h4>
+ * The finder returned by this method depends on this {@code
EPSGDataAccess} instance.
+ * The finder should not be used after this factory has been {@linkplain
#close() closed}
+ * or given back to the {@link EPSGFactory}.
+ *
* @return a finder to use for looking up unidentified objects.
* @throws FactoryException if the finder cannot be created.
*/
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index aed9fa2b7c..b6d6726fa9 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -92,7 +92,7 @@ enum TableInfo {
* Information about the "Conventional RS" table.
* This enumeration usually needs to be ignored because the current type
is too generic.
*
- * @see #isCandidate()
+ * @see #isSpecificEnough()
*/
CONVENTIONAL_RS(IdentifiedObject.class,
"\"Conventional RS\"",
@@ -281,7 +281,7 @@ enum TableInfo {
* Returns whether this enumeration value can be used when looking a table
by an object type.
* This method returns {@code false} for types that are too generic.
*/
- final boolean isCandidate() {
+ final boolean isSpecificEnough() {
return type != IdentifiedObject.class;
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
index cb257a5f5e..9fd0fe7db5 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
@@ -23,9 +23,9 @@ import org.apache.sis.util.privy.CollectionsExt;
/**
- * The domain of values of an EPSG parameter which accepts different units.
+ * The domain of values of an <abbr>EPSG</abbr> parameter which accepts
different units.
* An example is the EPSG:8617 (<cite>Coordinate 1 of evaluation point</cite>)
parameter,
- * which may be used in the EPSG database with either metres or degrees units.
+ * which may be used in the <abbr>EPSG</abbr> database with either metres or
degrees units.
*
* @author Martin Desruisseaux (Geomatys)
*/
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
index 907cdf0e43..978203d41f 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
@@ -23,8 +23,8 @@ import org.apache.sis.util.privy.AbstractMap;
/**
* A map which first looks for values in a user supplied map, then looks in a
default map if no value was found
- * in the user supplied map. This map is for {@link
org.apache.sis.referencing.factory.GeodeticObjectFactory} and
- * other SIS factories internal usage only.
+ * in the user supplied map. This map is for {@link
org.apache.sis.referencing.factory.GeodeticObjectFactory}
+ * and other <abbr>SIS</abbr> factories internal usage only.
*
* @author Martin Desruisseaux (Geomatys)
*/
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 786face47f..3c45c52ffc 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -344,6 +344,8 @@ class CoordinateOperationRegistry {
} catch (UnavailableFactoryException e) {
log(null, e);
codeFinder = null;
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(FactoryException.class);
}
authorityCodes.put(crs, codes);
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/FilteredIterator.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/FilteredIterator.java
new file mode 100644
index 0000000000..1d894f3410
--- /dev/null
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/FilteredIterator.java
@@ -0,0 +1,113 @@
+/*
+ * 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.referencing.privy;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+
+/**
+ * An iterator based on another iterator with some elements excluded.
+ * The exclusion is implemented by looking ahead what is next element.
+ * This iterator can also convert the element type.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <S> the type of elements returned by the source iterator.
+ * @param <T> the type of elements returned by this iterator.
+ */
+public final class FilteredIterator<S,T> implements Iterator<T> {
+ /**
+ * The source iterator.
+ */
+ private final Iterator<S> source;
+
+ /**
+ * Converter from elements of the source iterator to elements of this
iterator.
+ * If the mapper returns {@code null}, then that element will be skipped.
+ */
+ private final Function<S,T> mapper;
+
+ /**
+ * The next element to return, or {@code null} if unknown.
+ */
+ private T next;
+
+ /**
+ * Creates a new filtered iterator.
+ * Elements will be converted using the given mapper.
+ * If the mapper returns {@code null} for a given element, then that
element will be skipped.
+ *
+ * @param source the source iterator.
+ * @param mapper converter from elements of the source iterator to
elements of this iterator.
+ */
+ public FilteredIterator(final Iterator<S> source, final Function<S,T>
mapper) {
+ this.source = source;
+ this.mapper = mapper;
+ }
+
+ /**
+ * Returns {@code true} if the iteration has more non-null elements.
+ */
+ @Override
+ public boolean hasNext() {
+ while (next == null) {
+ if (source.hasNext()) {
+ next = mapper.apply(source.next());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the next non-null element in the iteration.
+ *
+ * @throws NoSuchElementException if the iteration has no more elements.
+ */
+ @Override
+ public T next() {
+ T element = next;
+ next = null;
+ while (element == null) {
+ // We will let the source throws NoSuchElementException.
+ element = mapper.apply(source.next());
+ }
+ return element;
+ }
+
+ /**
+ * Performs the given action for each remaining element.
+ */
+ @Override
+ public void forEachRemaining(final Consumer<? super T> action) {
+ T element = next;
+ if (element != null) {
+ next = null;
+ action.accept(element);
+ }
+ while (source.hasNext()) {
+ element = mapper.apply(source.next());
+ if (element != null) {
+ action.accept(element);
+ }
+ }
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
index 5183689e4e..9035a15b69 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
@@ -25,7 +25,7 @@ import org.apache.sis.util.privy.SetOfUnknownSize;
/**
* An unmodifiable set built from an iterator, which will be filled only when
needed.
* This implementation does <strong>not</strong> check if all elements in the
iterator
- * are really unique; we assume that this condition was already verified by
the caller.
+ * are really unique. We assume that this condition was already verified by
the caller.
*
* <p>Some usages for this class are to prepend some values before the
elements given by the source {@code Iterable},
* or to replace some values when they are loaded.</p>
@@ -35,6 +35,8 @@ import org.apache.sis.util.privy.SetOfUnknownSize;
*
* @author Martin Desruisseaux (IRD, Geomatys)
*
+ * @see FilteredIterator
+ *
* @param <E> the type of elements in the set.
*/
public abstract class LazySet<E> extends SetOfUnknownSize<E> {
@@ -66,11 +68,11 @@ public abstract class LazySet<E> extends
SetOfUnknownSize<E> {
}
/**
- * Creates the iterator which will provide the elements of this set before
filtering.
+ * Creates the iterator which will provide the elements of this set.
* This method will be invoked only when first needed and at most once,
unless {@link #reload()} is invoked.
* After creation, calls to {@link Iterator#next()} will also be done only
when first needed.
*
- * @return iterator over the elements of this set before filtering.
+ * @return iterator over the elements of this set.
*/
protected abstract Iterator<? extends E> createSourceIterator();
@@ -145,26 +147,13 @@ public abstract class LazySet<E> extends
SetOfUnknownSize<E> {
public final synchronized int size() {
if (canPullMore()) {
while (sourceIterator.hasNext()) {
- cache(next(sourceIterator));
+ cache(sourceIterator.next());
}
sourceIterator = null;
}
return numCached;
}
- /**
- * Returns the next element from the given iterator. Default
implementation returns {@link Iterator#next()}.
- * Subclasses may override if they need to apply additional processing.
For example, this method can be used
- * for skipping data, but this approach works only if we have the
guarantee that another element exists after
- * the skipped one (because {@code LazySet} will not invoke {@link
Iterator#hasNext()} again).
- *
- * @param it the iterator from which to get a next value.
- * @return the next value (may be {@code null}).
- */
- protected E next(final Iterator<? extends E> it) {
- return it.next();
- }
-
/**
* Caches a new element. This method is invoked by {@code LazySet} inside
a synchronized block.
* Subclasses could override this method if they want to substitute the
given value by another value.
@@ -205,7 +194,7 @@ public abstract class LazySet<E> extends
SetOfUnknownSize<E> {
assert index <= numCached : index;
if (index >= numCached) {
if (canPullMore()) {
- cache(next(sourceIterator));
+ cache(sourceIterator.next());
} else {
throw new NoSuchElementException();
}
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
index 32be48bacf..09e350c3a3 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
@@ -165,7 +165,7 @@ public final class ConcurrentAuthorityFactoryTest extends
TestCase {
assertTrue (createdDAOs.get(1).isClosed(), "Worker should
be disposed.");
assertTrue (createdDAOs.get(0).isClosed(), "Worker should
be disposed.");
}
- // If the garbage collector didn't complete, report as a skipped test
instead than a test failure.
+ // If the garbage collector didn't complete, report as a skipped test
instead of a test failure.
assumeTrue(r1 & r2, "The execution of
ConcurrentAuthorityFactory.disposeExpired() could not complete.");
}
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
index eb7371b579..671fb48832 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
@@ -20,7 +20,6 @@ import java.util.Map;
import org.opengis.util.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.apache.sis.referencing.crs.DefaultGeographicCRS;
@@ -73,7 +72,7 @@ public final class IdentifiedObjectFinderTest extends
TestCase {
* Same test as above, using a CRS without identifier.
* The intent is to force a full scan.
*/
- final CoordinateReferenceSystem search = new DefaultGeographicCRS(
+ final var search = new DefaultGeographicCRS(
Map.of(DefaultGeographicCRS.NAME_KEY, CRS84.getName()),
CRS84.getDatum(), CRS84.getDatumEnsemble(),
CRS84.getCoordinateSystem());
assertEqualsIgnoreMetadata(CRS84, search); // Required
condition for next test.
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index bf8525cc65..68c7b489ff 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -887,7 +887,7 @@ public final class EPSGFactoryTest extends TestCaseWithLogs
{
public void testFindGeographic() throws FactoryException {
final EPSGFactory factory = dataEPSG.factory();
final IdentifiedObjectFinder finder =
factory.newIdentifiedObjectFinder();
- final DefaultGeographicCRS crs = (DefaultGeographicCRS) CRS.fromWKT(
+ final var crs = (DefaultGeographicCRS) CRS.fromWKT(
"GEOGCS[“WGS 84”,\n" +
" DATUM[“WGS 84”,\n" + // Use the alias instead of
primary name for forcing a deeper search.
" SPHEROID[“WGS 1984”, 6378137.0, 298.257223563]],\n" + //
Different name for forcing a deeper search.
@@ -904,29 +904,17 @@ public final class EPSGFactoryTest extends
TestCaseWithLogs {
assertTrue(finder.find(crs.forConvention(AxesConvention.NORMALIZED)).isEmpty(),
"Should not find WGS84 because the axis order is not the
same.");
/*
- * Ensure that the cache is empty.
+ * Performs a search based only on the name.
*/
finder.setSearchDomain(IdentifiedObjectFinder.Domain.DECLARATION);
- assertTrue(finder.find(crs).isEmpty(),
- "Should not find without a full scan, because the WKT
contains no identifier " +
- "and the CRS name is ambiguous (more than one EPSG object
have this name).");
- /*
- * Scan the database for searching the CRS.
- */
- finder.setSearchDomain(IdentifiedObjectFinder.Domain.ALL_DATASET);
final IdentifiedObject found = finder.findSingleton(crs);
- assertNotNull(found, "With full scan allowed, the CRS should be
found.");
assertEpsgNameAndIdentifierEqual("WGS 84", 4326, found);
/*
- * Search should behave as specified by `DECLARATION` contract even if
the CRS is in the cache.
- */
- finder.setSearchDomain(IdentifiedObjectFinder.Domain.DECLARATION);
- assertNull(finder.findSingleton(crs), "Should met `DECLARATION`
contract.");
- /*
- * Should find the CRS without the need of a full scan, because of the
cache.
+ * Scan the database for searching the CRS.
*/
- finder.setSearchDomain(IdentifiedObjectFinder.Domain.ALL_DATASET);
- assertSame(found, finder.findSingleton(crs), "The CRS should still in
the cache.");
+
finder.setSearchDomain(IdentifiedObjectFinder.Domain.EXHAUSTIVE_VALID_DATASET);
+ assertEpsgNameAndIdentifierEqual("WGS 84", 4326,
finder.findSingleton(crs));
+ assertSame(found, finder.findSingleton(crs), "The CRS should be in the
cache.");
loggings.assertNoUnexpectedLog();
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index af7647a142..a0506facf7 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@ -55,6 +55,7 @@ import org.apache.sis.util.Localized;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.Workaround;
import org.apache.sis.util.privy.Constants;
+import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.io.wkt.Convention;
import org.apache.sis.io.wkt.WKTFormat;
import org.apache.sis.io.wkt.Warnings;
@@ -643,7 +644,12 @@ public class InfoStatements implements Localized,
AutoCloseable {
if (cached != null) {
return cached;
}
- result = findOrAddCRS(crs);
+ try {
+ result = findOrAddCRS(crs);
+ } catch (BackingStoreException e) {
+ // May be thrown by IdentifiedObjectFinder iterator.
+ throw e.unwrapOrRethrow(FactoryException.class);
+ }
database.cacheOfSRID.put(crs, result.srid);
}
CommonExecutor.instance().submit((Runnable) result);
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
index 999ba40b18..c22f17689b 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
@@ -91,7 +91,7 @@ public abstract class SetOfUnknownSize<E> extends
AbstractSet<E> {
@Override
public boolean removeAll(final Collection<?> c) {
/*
- * Do not invoke super.removeAll(c) even if isSizeKnown() returns
'true' because we want to unconditionally
+ * Do not invoke super.removeAll(c) even if isSizeKnown() returns
`true` because we want to unconditionally
* iterate over the elements of the given collection. The reason is
that this Set may compute the values in
* a dynamic way and it is sometimes difficult to ensure that the
values returned by this Set's iterator are
* fully consistent with the values recognized by contains(Object) and
remove(Object) methods. Furthermore,
@@ -173,7 +173,7 @@ public abstract class SetOfUnknownSize<E> extends
AbstractSet<E> {
@Override
public boolean equals(final Object object) {
/*
- * Do not invoke super.equals(object) even if isSizeKnown() returns
'true' because we want to unconditionally
+ * Do not invoke super.equals(object) even if isSizeKnown() returns
`true` because we want to unconditionally
* iterate over the elements of this Set. The reason is that this Set
may compute the values dynamically and
* it is sometimes difficult to ensure that this Set's iterator is
fully consistent with the values recognized
* by the contains(Object) method. For example, the iterator may
return "EPSG:4326" while the contains(Object)
@@ -186,7 +186,7 @@ public abstract class SetOfUnknownSize<E> extends
AbstractSet<E> {
if (!(object instanceof Set<?>)) {
return false;
}
- final Set<?> that = (Set<?>) object;
+ final var that = (Set<?>) object;
int size = 0;
for (final Iterator<E> it = iterator(); it.hasNext();) {
if (!that.contains(it.next())) {
diff --git a/netbeans-project/nbproject/project.xml
b/netbeans-project/nbproject/project.xml
index f578f8c466..b642dd80ff 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -34,6 +34,7 @@
<word>Geopackage</word>
<word>geospatial</word>
<word>Molodensky</word>
+ <word>namespace</word>
<word>transformative</word>
<word>untiled</word>
</spellchecker-wordlist>
diff --git
a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
index 95b646aaa1..89110202f0 100644
---
a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
+++
b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
@@ -126,7 +126,7 @@ final class Generator extends ScriptProvider {
* Copies the <abbr>EPSG</abbr> terms of use from the {@code sis-epsg}
module to this {@code sis-embedded-data} module.
* If the <abbr>EPSG</abbr> data are not found, then this method does
nothing.
*
- * <p>We copy those files ourselves instead than relying on {@code
maven-resources-plugin}
+ * <p>We copy those files ourselves instead of relying on {@code
maven-resources-plugin}
* because a future version may combine more licenses in a single file.</p>
*/
private void copyLicenseFiles() throws URISyntaxException, IOException {