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 6326b1db77 Support the vertical and temporal components of extents
declared in EPSG version 10+.
6326b1db77 is described below
commit 6326b1db77311e13444f1b011cdbad71ed66d449
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Jul 26 12:48:24 2025 +0200
Support the vertical and temporal components of extents declared in EPSG
version 10+.
A difficulty is that the `VerticalExtent` may contain a `VerticalCRS`.
Resolving that CRS causes a potentially recursive call to the methods
at `createProperties(…)` invocation time.
---
.../metadata/iso/extent/DefaultVerticalExtent.java | 18 +-
.../apache/sis/metadata/iso/extent/Extents.java | 8 +-
.../referencing/factory/sql/EPSGDataAccess.java | 726 +++++++++++++--------
.../sis/referencing/factory/sql/SQLTranslator.java | 58 +-
.../main/org/apache/sis/util/collection/Cache.java | 11 +-
5 files changed, 491 insertions(+), 330 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
index 04185e917c..d9b1e63c9a 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
@@ -92,11 +92,13 @@ public class DefaultVerticalExtent extends ISOMetadata
implements VerticalExtent
private Double maximumValue;
/**
- * Provides information about the vertical coordinate reference system to
- * which the maximum and minimum elevation values are measured. The CRS
- * identification includes unit of measure.
+ * Provides information about the vertical coordinate reference system
+ * to which the maximum and minimum elevation values are measured.
+ * The <abbr>CRS</abbr> identification includes unit of measure.
+ *
+ * @see #getVerticalCRS()
*/
- @SuppressWarnings("serial")
+ @SuppressWarnings("serial") // Apache SIS implementations are
serializable.
private VerticalCRS verticalCRS;
/**
@@ -209,11 +211,11 @@ public class DefaultVerticalExtent extends ISOMetadata
implements VerticalExtent
}
/**
- * Provides information about the vertical coordinate reference system to
- * which the maximum and minimum elevation values are measured.
- * The CRS identification includes unit of measure.
+ * Provides information about the vertical coordinate reference system
+ * to which the maximum and minimum elevation values are measured.
+ * The <abbr>CRS</abbr> identification includes unit of measure.
*
- * @return the vertical CRS, or {@code null}.
+ * @return the vertical <abbr>CRS</abbr>, or {@code null}.
*
* @see <a href="https://issues.apache.org/jira/browse/SIS-397">SIS-397</a>
*/
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
index 50475c36c8..8458017128 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
@@ -421,8 +421,8 @@ public final class Extents extends Static {
RealizationMethod selectedMethod = null;
if (extent != null) try {
for (final VerticalExtent element :
nonNull(extent.getVerticalElements())) {
- double min = element.getMinimumValue();
- double max = element.getMaximumValue();
+ Double min = element.getMinimumValue();
+ Double max = element.getMaximumValue();
final VerticalCRS crs = element.getVerticalCRS();
RealizationMethod method = null;
Unit<?> unit = null;
@@ -434,11 +434,13 @@ public final class Extents extends Static {
final CoordinateSystemAxis axis =
crs.getCoordinateSystem().getAxis(0);
unit = axis.getUnit();
if (axis.getDirection() == AxisDirection.DOWN) {
- final double tmp = min;
+ final Double tmp = min;
min = -max;
max = -tmp;
}
}
+ if (min == null) min = Double.NEGATIVE_INFINITY;
+ if (max == null) max = Double.POSITIVE_INFINITY;
if (range != null) {
/*
* If the new range does not specify any realization
method or unit, we do not know how
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 1decf179da..3a3a7c121b 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
@@ -49,6 +49,7 @@ import javax.measure.format.MeasurementParseException;
import org.opengis.util.NameSpace;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
+import org.opengis.util.Factory;
import org.opengis.util.FactoryException;
import org.opengis.util.NoSuchIdentifierException;
import org.opengis.metadata.extent.Extent;
@@ -116,6 +117,8 @@ import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
import org.apache.sis.metadata.iso.extent.DefaultExtent;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
+import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
import org.apache.sis.metadata.sql.privy.SQLUtilities;
import org.apache.sis.measure.MeasurementRange;
import org.apache.sis.measure.NumberRange;
@@ -201,6 +204,12 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
return code;
}
+ /**
+ * String sometime used in the <abbr>EPSG</abbr> database for unknown
scope.
+ * If Apache <abbr>SIS</abbr>, this is replaced by {@code null}.
+ */
+ private static final String UNKNOWN_SCOPE = "?";
+
/**
* The namespace of EPSG names and codes. This namespace is needed by all
{@code createFoo(String)} methods.
* The {@code EPSGDataAccess} constructor relies on the {@link
EPSGFactory#nameFactory} caching mechanism
@@ -288,15 +297,27 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
/**
* A safety guard for preventing never-ending loops in recursive calls to
some {@code createFoo(String)} methods.
- * Recursion may happen while creating Bursa-Wolf parameters, projected
CRS if the database has erroneous data,
- * compound CRS if there is cycles, or coordinate operations.
+ * Recursion may theoretically happen during the creation of the following
objects:
*
- * <h4>Example</h4>
- * {@link #createDatum(String)} invokes {@link
#createBursaWolfParameters(PrimeMeridian, Integer)}, which creates
- * a target datum. The target datum could have its own Bursa-Wolf
parameters, with one of them pointing again to
- * the source datum.
+ * <ul>
+ * <li>Bursa-Wolf parameters, as they can be created with a datum and
reference another datum.</li>
+ * <li>projected <abbr>CRS</abbr> if the database contains cycles (it
would be an error in the database).</li>
+ * <li>Compound <abbr>CRS</abbr> if the database contains cycles (it
would be an error in the database).</li>
+ * <li>Coordinate operations if the database contains cycles (it would
be an error in the database).</li>
+ * <li>Extent created by a <abbr>CRS</abbr> as the extent may itself
reference a vertical <abbr>CRS</abbr>.</li>
+ * </ul>
*
- * Keys are EPSG codes and values are the type of object being constructed
(but those values are not yet used).
+ * The database distributed by <abbr>EPSG</abbr> avoids the above-cited
cycles, for example by setting
+ * the <abbr>CRS</abbr> code to {@code null} instead of putting a value
that would create a cycle.
+ * Apache <abbr>SIS</abbr> nevertheless check by safety, since the
database can be user-provided.
+ *
+ * <p>Keys are <abbr>EPSG</abbr> codes and each value is the type of the
object identified by the code.
+ * The values are currently used only for consistency checks. There is a
risk of key collision because
+ * the same <abbr>EPSG</abbr> code can be used for different types of
objects. But for this algorithm,
+ * key collision is a problem only if it happens in a cycle. We presume
that it does not happen.</p>
+ *
+ * @see #ensureNoCycle(Class, Integer)
+ * @see #endOfCycleCheck(Class, Integer)
*/
private final Map<Integer, Class<?>> safetyGuard = new HashMap<>();
@@ -948,7 +969,6 @@ codes: for (int i=0; i<codes.length; i++) {
final ResultSetMetaData metadata = result.getMetaData();
final String column = metadata.getColumnName(columnIndex);
final String table = metadata.getTableName (columnIndex);
- result.close(); // Only an optimization. The actual
try-with-resource is done by the caller.
return error().getString(Errors.Keys.NullValueInTable_3, table,
column, code);
}
@@ -1064,53 +1084,29 @@ codes: for (int i=0; i<codes.length; i++) {
* try {
* ...
* } finally {
- * endOfRecursive(type, code);
+ * endOfCycleCheck(type, code);
* }
* }
+ *
+ * @param type the type of object identified by the given code.
+ * @param code <abbr>EPSG</abbr> code of the object of the specified
type.
+ * @throws FactoryDataException if recursive construction has been
detected.
*/
- private void ensureNoCycle(final Class<?> type, final Integer code) throws
FactoryException {
+ private void ensureNoCycle(final Class<?> type, final Integer code) throws
FactoryDataException {
if (safetyGuard.putIfAbsent(code, type) != null) {
- throw new
FactoryException(resources().getString(Resources.Keys.RecursiveCreateCallForCode_2,
type, code));
+ throw new
FactoryDataException(resources().getString(Resources.Keys.RecursiveCreateCallForCode_2,
type, code));
}
}
/**
* Invoked after the block protected against infinite recursion.
*/
- private void endOfRecursion(final Class<?> type, final Integer code)
throws FactoryException {
+ private void endOfCycleCheck(final Class<?> type, final Integer code)
throws FactoryException {
if (safetyGuard.remove(code) != type) {
throw new FactoryException(String.valueOf(code)); // Would be an
EPSGDataAccess bug if it happen.
}
}
- /**
- * Get all domains for the object identified by the given code.
- * If found, the domains are added in the {@link #properties} map.
- * The table used by this method is new in version 10 of EPSG database.
- *
- * @param table the table of the object for which to get usage.
- * @param code the code for the object in the given table.
- */
- private void addDomainProperty(final String table, final Integer code)
throws SQLException, FactoryException {
- final var domains = new ArrayList<ObjectDomain>();
- try (ResultSet result = executeMetadataQuery("Usage",
- "SELECT EXTENT_CODE, SCOPE_CODE FROM \"Usage\"" +
- " WHERE OBJECT_TABLE_NAME=? AND OBJECT_CODE=?",
- translator.toActualTableName(table), code))
- {
- while (result.next()) {
- final Extent extent = createExtent(getString(code, result, 1));
- final InternationalString scope = getScope(getInteger(code,
result, 2));
- if (scope != null || extent != null) {
- domains.add(new DefaultObjectDomain(scope, extent));
- }
- }
- }
- if (!domains.isEmpty()) {
- properties.put(IdentifiedObject.DOMAINS_KEY,
domains.toArray(ObjectDomain[]::new));
- }
- }
-
/**
* Returns the scope from the given authority code.
*
@@ -1121,9 +1117,12 @@ codes: for (int i=0; i<codes.length; i++) {
final Long cacheKey = cacheKey(1, code);
var scope = (InternationalString) localCache.get(cacheKey);
if (scope == null) {
- try (ResultSet rs = executeQuery("Scope", "SELECT SCOPE FROM
\"Scope\" WHERE SCOPE_CODE=?", code)) {
- while (rs.next()) {
- scope =
ensureSingleton(Types.toInternationalString(rs.getString(1)), scope, code);
+ try (ResultSet result = executeQuery("Scope", "SELECT SCOPE FROM
\"Scope\" WHERE SCOPE_CODE=?", code)) {
+ while (result.next()) {
+ String s = result.getString(1);
+ if (!UNKNOWN_SCOPE.equals(s)) {
+ scope =
ensureSingleton(Types.toInternationalString(s), scope, code);
+ }
}
}
localCache.put(cacheKey, scope);
@@ -1183,11 +1182,25 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
/**
* Returns the name and aliases for the {@link IdentifiedObject} to
construct.
*
+ * <h4>Possible recursive calls</h4>
+ * Invoking this method may cause a recursive call to {@link
#createCoordinateReferenceSystem(String)}
+ * because this method may create a vertical extent, which may contain a
{@link VerticalCRS} property.
+ * A recursive <abbr>CRS</abbr> creation may cause some {@link ResultSet}s
to be closed when the cached
+ * {@link PreparedStatement}s are reused. Callers should use a loop like
below:
+ *
+ * {@snippet lang="java" :
+ * while (result.next()) { // Expect a singleton, but loop as a
safety.
+ * // Get values of columns, then invoke `createProperties(…)`
last.
+ * returnValue = ensureSingleton(currentValue, returnValue, code);
+ * if (result.isClosed()) break;
+ * }
+ * }
+ *
* @param table the table on which a query has been executed.
* @param code the EPSG code of the object to construct.
* @param name the name for the {@link IdentifiedObject} to
construct.
* @param description a description associated with the name, or {@code
null} if none.
- * @param extent extent code, or {@code null} if none. This is a
legacy of <abbr>EPSG</abbr> version 9.
+ * @param extentCode extent code, or {@code null} if none. This is a
legacy of <abbr>EPSG</abbr> version 9.
* @param scope the scope, or {@code null} if none. This is a
legacy of <abbr>EPSG</abbr> version 9.
* @param remarks remarks as a {@link String} or {@link
InternationalString}, or {@code null} if none.
* @param deprecated {@code true} if the object to create is deprecated.
@@ -1198,12 +1211,44 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final Integer code,
String name,
// May be replaced by an alias.
final CharSequence description,
- final String extent,
// Legacy from EPSG version 9.
+ final String extentCode,
// Legacy from EPSG version 9.
final String scope,
// Legacy from EPSG version 9.
final CharSequence remarks,
final boolean deprecated)
throws SQLException, FactoryException
{
+ /*
+ * Request for extent may cause a recursive call to
`createCoordinateReferenceSystem(…)`,
+ * se we need to fetch and store the extent before to populate the
`properties` map.
+ */
+ final Extent extent = (extentCode == null) ? null :
createExtent(extentCode);
+ /*
+ * Get all domains for the object identified by the given code.
+ * The table used nere is new in version 10 of EPSG database.
+ * We have to create the extents outside the `while` loop for
+ * the same reason as above for `extent`.
+ */
+ ObjectDomain[] domains = null;
+ if (translator.isUsageTableFound()) {
+ final var extents = new ArrayList<String>();
+ final var scopes = new ArrayList<InternationalString>();
+ try (ResultSet result = executeMetadataQuery("Usage",
+ "SELECT EXTENT_CODE, SCOPE_CODE FROM \"Usage\"" +
+ " WHERE OBJECT_TABLE_NAME=? AND OBJECT_CODE=?",
+ translator.toActualTableName(table), code))
+ {
+ while (result.next()) {
+ extents.add(getString(code, result, 1));
+ scopes .add(getScope(getInteger(code, result, 2)));
+ }
+ }
+ if (!extents.isEmpty()) {
+ domains = new ObjectDomain[extents.size()];
+ for (int i=0; i<domains.length; i++) {
+ domains[i] = new DefaultObjectDomain(scopes.get(i),
owner.createExtent(extents.get(i)));
+ }
+ }
+ }
/*
* Search for aliases. Note that searching for the object code is not
sufficient. We also need to check if the
* record is really from the table we are looking for since different
tables may have objects with the same ID.
@@ -1293,14 +1338,14 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
properties.put(IdentifiedObject.REMARKS_KEY, remarks);
properties.put(AbstractIdentifiedObject.LOCALE_KEY, locale);
properties.put(ReferencingFactoryContainer.MT_FACTORY,
owner.mtFactory);
- if (translator.isUsageTableFound()) {
- addDomainProperty(table, code); // Intersection
table new since EPSG version 10.
+ if (domains != null) {
+ properties.put(IdentifiedObject.DOMAINS_KEY, domains);
}
- if (scope != null && !scope.equals("?")) { // Should be
always null since EPSG version 10.
+ if (scope != null && !scope.equals(UNKNOWN_SCOPE)) { // Should be
always null since EPSG version 10.
properties.put(ObjectDomain.SCOPE_KEY, scope);
}
- if (extent != null) { // Should be
always null since EPSG version 10.
- properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY,
owner.createExtent(extent));
+ if (extent != null) { // Should be
always null since EPSG version 10.
+ properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent);
}
return properties;
}
@@ -1398,6 +1443,23 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
throw noSuchAuthorityCode(IdentifiedObject.class, code);
}
+ /**
+ * The {@code CRSFactory.createFoo(…)} or {@code
DatumFactory.createFoo(…)} method to invoke.
+ * This is a convenience used by some {@link EPSGDataAccess} {@code
createFoo(String)} methods when
+ * the factory method to invoke has been decided but the properties map
has not yet been populated.
+ */
+ private interface FactoryCall<F extends Factory, R extends
IdentifiedObject> {
+ /**
+ * Creates a <abbr>CRS</abbr> or datum.
+ *
+ * @param factory the factory to use for creating the object.
+ * @param properties the properties to give to the object.
+ * @return the object created from the given properties.
+ * @throws FactoryException if the factory cannot create the object.
+ */
+ R create(F factory, Map<String, Object> properties) throws
FactoryException;
+ }
+
/**
* Creates an arbitrary coordinate reference system from a code.
* The returned object will typically be an instance of {@link
GeographicCRS}, {@link ProjectedCRS},
@@ -1424,7 +1486,6 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* @throws FactoryException if the object creation failed for some other
reason.
*/
@Override
- @SuppressWarnings("try") // Explicit call to close() on an
auto-closeable resource.
public synchronized CoordinateReferenceSystem
createCoordinateReferenceSystem(final String code)
throws NoSuchAuthorityCodeException, FactoryException
{
@@ -1456,76 +1517,102 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final boolean deprecated = getOptionalBoolean(result, 6);
final String type = getString (code, result, 7);
/*
- * Note: Do not invoke `createProperties` now, even if we have
all required information,
- * because the `properties` map is going to overwritten
by calls to `createDatum`, etc.
- *
+ * Do not invoke `createProperties` now, even if we have all
required information,
+ * because the `properties` map will be overwritten by calls
to `createDatum` and
+ * similar methods. Instead, remember the constructor to
invoke later.
+ */
+ final FactoryCall<CRSFactory, CoordinateReferenceSystem>
constructor;
+ /*
* The following switch statement should have a case for all
"epsg_crs_kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in
this Java code are in lower cases.
*/
- final CRSFactory crsFactory = owner.crsFactory;
- final CoordinateReferenceSystem crs;
switch (type.toLowerCase(Locale.US)) {
- /*
----------------------------------------------------------------------
- * GEOGRAPHIC CRS
+ /*
──────────────────────────────────────────────────────────────────────
+ * GEOCENTRIC CRS
*
- * NOTE: `createProperties` MUST be invoked after any
call to another
- * `createFoo` method. Consequently, do not factor
out.
- *
---------------------------------------------------------------------- */
- case "geographic 2d":
- case "geographic 3d": {
- Integer csCode = getInteger(code, result, 8);
- if (replaceDeprecatedCS) {
- csCode = replaceDeprecatedCS(csCode);
+ * NOTE: all values must be extracted from the
`ResultSet`
+ * before to invoke any `owner.createFoo(…)`
method.
+ *
────────────────────────────────────────────────────────────────────── */
+ case "geocentric": {
+ final CoordinateSystem cs;
+ final GeodeticDatum datumOrEnsemble;
+ ensureNoCycle(GeodeticCRS.class, epsg);
+ try {
+ String csCode = getString(code, result, 8);
+ String datumCode = getString(code, result, 9);
+ cs = owner.createCoordinateSystem(csCode); // Do
not inline the `getString(…)` calls.
+ datumOrEnsemble =
owner.createGeodeticDatum(datumCode);
+ } finally {
+ endOfCycleCheck(GeodeticCRS.class, epsg);
}
- final EllipsoidalCS cs =
owner.createEllipsoidalCS(csCode.toString());
- final String datumCode = getOptionalString(result, 9);
- GeodeticDatum datum;
- if (datumCode != null) {
- datum = owner.createGeodeticDatum(datumCode);
+ final DatumEnsemble<GeodeticDatum> ensemble =
wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class);
+ final GeodeticDatum datum = (ensemble == null) ?
datumOrEnsemble : null;
+ if (cs instanceof CartesianCS) {
+ final var c = (CartesianCS) cs;
+ constructor = (factory, metadata) ->
factory.createGeodeticCRS(metadata, datum, ensemble, c);
+ } else if (cs instanceof SphericalCS) {
+ final var c = (SphericalCS) cs;
+ constructor = (factory, metadata) ->
factory.createGeodeticCRS(metadata, datum, ensemble, c);
} else {
- final String geoCode = getString(code, result, 10,
9);
- result.close(); // Must be closed before call
to createGeographicCRS(String)
- ensureNoCycle(GeographicCRS.class, epsg);
- try {
- datum =
owner.createGeographicCRS(geoCode).getDatum();
- } finally {
- endOfRecursion(GeographicCRS.class, epsg);
+ throw new FactoryDataException(error().getString(
+ Errors.Keys.IllegalCoordinateSystem_1,
cs.getName()));
+ }
+ break;
+ }
+ /*
──────────────────────────────────────────────────────────────────────
+ * GEOGRAPHIC CRS
+ *
────────────────────────────────────────────────────────────────────── */
+ case "geographic 2d":
+ case "geographic 3d": {
+ final EllipsoidalCS cs;
+ final GeodeticDatum datumOrEnsemble;
+ ensureNoCycle(GeographicCRS.class, epsg);
+ try {
+ Integer csCode = getInteger(code, result, 8);
+ String datumCode = getOptionalString(result, 9);
+ if (datumCode == null) {
+ String baseCode = getString(code, result, 10,
9);
+ datumOrEnsemble =
owner.createGeographicCRS(baseCode).getDatum();
+ } else {
+ datumOrEnsemble =
owner.createGeodeticDatum(datumCode);
}
+ if (replaceDeprecatedCS) {
+ csCode = replaceDeprecatedCS(csCode);
+ }
+ cs = owner.createEllipsoidalCS(csCode.toString());
+ } finally {
+ endOfCycleCheck(GeographicCRS.class, epsg);
}
- DatumEnsemble<GeodeticDatum> ensemble =
wasDatumEnsemble(datum, GeodeticDatum.class);
- if (ensemble != null) datum = null;
- crs =
crsFactory.createGeographicCRS(createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated), datum, ensemble, cs);
+ final DatumEnsemble<GeodeticDatum> ensemble =
wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class);
+ final GeodeticDatum datum = (ensemble == null) ?
datumOrEnsemble : null;
+ constructor = (factory, metadata) ->
factory.createGeographicCRS(metadata, datum, ensemble, cs);
break;
}
- /*
----------------------------------------------------------------------
+ /*
──────────────────────────────────────────────────────────────────────
* PROJECTED CRS
*
- * NOTE: This method invokes itself indirectly, through
createGeographicCRS.
- * Consequently, we cannot use `result` anymore
after this block.
- *
---------------------------------------------------------------------- */
+ * NOTE: This method may invoke itself for creating the
base CRS,
+ * in which case the `ResultSet` will be closed.
Therefore,
+ * all values must be extracted from the
`ResultSet` before
+ * to invoke any `owner.createFoo(…)` method.
+ *
────────────────────────────────────────────────────────────────────── */
case "projected": {
- final String csCode = getString(code, result, 8);
- final String geoCode = getString(code, result, 10);
- final String opCode = getString(code, result, 11);
- result.close(); // Must be closed before call to
createFoo(String)
+ final CartesianCS cs;
+ final Conversion fromBase;
+ final CoordinateReferenceSystem baseCRS;
ensureNoCycle(ProjectedCRS.class, epsg);
try {
- final CartesianCS cs =
owner.createCartesianCS(csCode);
- final Conversion op;
+ final String csCode = getString(code, result,
8);
+ final String baseCode = getString(code, result,
10);
+ final String opCode = getString(code, result,
11);
try {
- op = (Conversion)
owner.createCoordinateOperation(opCode);
+ fromBase = (Conversion)
owner.createCoordinateOperation(opCode);
} catch (ClassCastException e) {
// Should never happen in a well-formed EPSG
database.
// If happen anyway, the ClassCastException
cause will give more hints than just the message.
throw (NoSuchAuthorityCodeException)
noSuchAuthorityCode(Conversion.class, opCode).initCause(e);
}
- final CoordinateReferenceSystem baseCRS;
- final boolean suspendParamChecks;
- if (!deprecated) {
- baseCRS =
owner.createCoordinateReferenceSystem(geoCode);
- suspendParamChecks = true;
- } else {
+ if (deprecated) {
/*
* If the ProjectedCRS is deprecated, one
reason among others may be that it uses one of
* the deprecated coordinate systems. Those
deprecated CS used non-linear units like DMS.
@@ -1540,21 +1627,27 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
try {
quiet = true;
replaceDeprecatedCS = true;
- baseCRS =
createCoordinateReferenceSystem(geoCode); // Do not cache that CRS.
+ baseCRS =
createCoordinateReferenceSystem(baseCode); // Do not cache that CRS.
} finally {
replaceDeprecatedCS = false;
quiet = old;
}
- /*
- * The crsFactory method calls will indirectly
create a parameterized MathTransform.
- * Their constructor will try to verify the
parameter validity. But some deprecated
- * CRS had invalid parameter values (they were
deprecated precisely for that reason).
- * If and only if we are creating a deprecated
CRS, temporarily suspend the parameter
- * checks.
- */
- suspendParamChecks =
Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK);
- // Try block must be immediately after above
line (do not insert any code between).
+ } else {
+ baseCRS =
owner.createCoordinateReferenceSystem(baseCode); // Use the cache.
}
+ cs = owner.createCartesianCS(csCode);
+ } finally {
+ endOfCycleCheck(ProjectedCRS.class, epsg);
+ }
+ constructor = (factory, metadata) -> {
+ /*
+ * The crsFactory method calls will indirectly
create a parameterized MathTransform.
+ * Their constructor will try to verify the
parameter validity. But some deprecated
+ * CRS had invalid parameter values (they were
deprecated precisely for that reason).
+ * If and only if we are creating a deprecated
CRS, temporarily suspend the parameter
+ * checks.
+ */
+ final boolean old = !deprecated ||
Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK);
try {
/*
* For a ProjectedCRS, the baseCRS is always
geodetic. So in theory we would not
@@ -1567,129 +1660,117 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* We need to check if EPSG database 10+ has
more specific information.
* See
https://issues.apache.org/jira/browse/SIS-518
*/
-
@SuppressWarnings("LocalVariableHidesMemberVariable")
- final Map<String,Object> properties =
createProperties("Coordinate Reference System",
- epsg, name, null, area, scope,
remarks, deprecated);
if (baseCRS instanceof GeodeticCRS) {
- crs =
crsFactory.createProjectedCRS(properties, (GeodeticCRS) baseCRS, op, cs);
+ return
factory.createProjectedCRS(metadata, (GeodeticCRS) baseCRS, fromBase, cs);
} else {
- crs =
crsFactory.createDerivedCRS(properties, baseCRS, op, cs);
+ return factory.createDerivedCRS(metadata,
baseCRS, fromBase, cs);
}
} finally {
-
Semaphores.clear(Semaphores.SUSPEND_PARAMETER_CHECK, suspendParamChecks);
+
Semaphores.clear(Semaphores.SUSPEND_PARAMETER_CHECK, old);
}
- } finally {
- endOfRecursion(ProjectedCRS.class, epsg);
- }
+ };
break;
}
- /*
----------------------------------------------------------------------
+ /*
──────────────────────────────────────────────────────────────────────
* VERTICAL CRS
- *
---------------------------------------------------------------------- */
+ *
────────────────────────────────────────────────────────────────────── */
case "vertical": {
- VerticalCS cs = owner.createVerticalCS
(getString(code, result, 8));
- VerticalDatum datum =
owner.createVerticalDatum(getString(code, result, 9));
- DatumEnsemble<VerticalDatum> ensemble =
wasDatumEnsemble(datum, VerticalDatum.class);
- if (ensemble != null) datum = null;
- crs =
crsFactory.createVerticalCRS(createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated), datum, ensemble, cs);
+ final VerticalCS cs;
+ final VerticalDatum datumOrEnsemble;
+ ensureNoCycle(VerticalCRS.class, epsg);
+ try {
+ final String csCode = getString(code, result,
8);
+ final String datumCode = getString(code, result,
9);
+ cs = owner.createVerticalCS(csCode); // Do not
inline the `getString(…)` calls.
+ datumOrEnsemble =
owner.createVerticalDatum(datumCode);
+ } finally {
+ endOfCycleCheck(VerticalCRS.class, epsg);
+ }
+ final DatumEnsemble<VerticalDatum> ensemble =
wasDatumEnsemble(datumOrEnsemble, VerticalDatum.class);
+ final VerticalDatum datum = (ensemble == null) ?
datumOrEnsemble : null;
+ constructor = (factory, metadata) ->
factory.createVerticalCRS(metadata, datum, ensemble, cs);
break;
}
- /*
----------------------------------------------------------------------
+ /*
──────────────────────────────────────────────────────────────────────
* TEMPORAL CRS
*
- * NOTE : The original EPSG database does not define any
temporal CRS.
- * This block is a SIS-specific extension.
- *
---------------------------------------------------------------------- */
+ * NOTE : As of version 12, the EPSG database does not
define temporal CRS.
+ * This block is a SIS─specific extension.
+ *
────────────────────────────────────────────────────────────────────── */
case "time":
case "temporal": {
- TimeCS cs = owner.createTimeCS
(getString(code, result, 8));
- TemporalDatum datum =
owner.createTemporalDatum(getString(code, result, 9));
- DatumEnsemble<TemporalDatum> ensemble =
wasDatumEnsemble(datum, TemporalDatum.class);
- if (ensemble != null) datum = null;
- crs =
crsFactory.createTemporalCRS(createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated), datum, ensemble, cs);
+ final String csCode = getString(code, result, 8);
+ final String datumCode = getString(code, result, 9);
+ final TimeCS cs = owner.createTimeCS(csCode); // Do
not inline the `getString(…)` calls.
+ final TemporalDatum datumOrEnsemble =
owner.createTemporalDatum(datumCode);
+ final DatumEnsemble<TemporalDatum> ensemble =
wasDatumEnsemble(datumOrEnsemble, TemporalDatum.class);
+ final TemporalDatum datum = (ensemble == null) ?
datumOrEnsemble : null;
+ constructor = (factory, metadata) ->
factory.createTemporalCRS(metadata, datum, ensemble, cs);
+ break;
+ }
+ /*
──────────────────────────────────────────────────────────────────────
+ * ENGINEERING CRS
+ *
────────────────────────────────────────────────────────────────────── */
+ case "engineering": {
+ final String csCode = getString(code, result, 8);
+ final String datumCode = getString(code, result, 9);
+ final CoordinateSystem cs =
owner.createCoordinateSystem(csCode); // Do not inline the `getString(…)`
calls.
+ final EngineeringDatum datumOrEnsemble =
owner.createEngineeringDatum(datumCode);
+ final DatumEnsemble<EngineeringDatum> ensemble =
wasDatumEnsemble(datumOrEnsemble, EngineeringDatum.class);
+ final EngineeringDatum datum = (ensemble == null) ?
datumOrEnsemble : null;
+ constructor = (factory, metadata) ->
factory.createEngineeringCRS(metadata, datum, ensemble, cs);
break;
}
- /*
----------------------------------------------------------------------
+ /*
──────────────────────────────────────────────────────────────────────
+ * PARAMETRIC CRS
+ *
────────────────────────────────────────────────────────────────────── */
+ case "parametric": {
+ final String csCode = getString(code, result, 8);
+ final String datumCode = getString(code, result, 9);
+ final ParametricCS cs =
owner.createParametricCS(csCode); // Do not inline the `getString(…)` calls.
+ final ParametricDatum datumOrEnsemble =
owner.createParametricDatum(datumCode);
+ final DatumEnsemble<ParametricDatum> ensemble =
wasDatumEnsemble(datumOrEnsemble, ParametricDatum.class);
+ final ParametricDatum datum = (ensemble == null) ?
datumOrEnsemble : null;
+ constructor = (factory, metadata) ->
factory.createParametricCRS(metadata, datum, ensemble, cs);
+ break;
+ }
+ /*
──────────────────────────────────────────────────────────────────────
* COMPOUND CRS
*
* NOTE: This method invokes itself recursively.
* Consequently, we cannot use `result` anymore.
- *
---------------------------------------------------------------------- */
+ *
────────────────────────────────────────────────────────────────────── */
case "compound": {
final String code1 = getString(code, result, 12);
final String code2 = getString(code, result, 13);
- result.close();
- final CoordinateReferenceSystem crs1, crs2;
- ensureNoCycle(CompoundCRS.class, epsg);
- try {
- crs1 =
owner.createCoordinateReferenceSystem(code1);
- crs2 =
owner.createCoordinateReferenceSystem(code2);
- } finally {
- endOfRecursion(CompoundCRS.class, epsg);
- }
- // Note: Do not invoke `createProperties` sooner.
- crs =
crsFactory.createCompoundCRS(createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated), crs1, crs2);
- break;
- }
- /*
----------------------------------------------------------------------
- * GEOCENTRIC CRS
- *
---------------------------------------------------------------------- */
- case "geocentric": {
- CoordinateSystem cs =
owner.createCoordinateSystem(getString(code, result, 8));
- GeodeticDatum datum = owner.createGeodeticDatum
(getString(code, result, 9));
- DatumEnsemble<GeodeticDatum> ensemble =
wasDatumEnsemble(datum, GeodeticDatum.class);
- if (ensemble != null) datum = null;
- @SuppressWarnings("LocalVariableHidesMemberVariable")
- final Map<String,Object> properties =
createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated);
- if (cs instanceof CartesianCS) {
- crs = crsFactory.createGeodeticCRS(properties,
datum, ensemble, (CartesianCS) cs);
- } else if (cs instanceof SphericalCS) {
- crs = crsFactory.createGeodeticCRS(properties,
datum, ensemble, (SphericalCS) cs);
- } else {
- throw new FactoryDataException(error().getString(
- Errors.Keys.IllegalCoordinateSystem_1,
cs.getName()));
- }
+ final CoordinateReferenceSystem crs1 =
owner.createCoordinateReferenceSystem(code1);
+ final CoordinateReferenceSystem crs2 =
owner.createCoordinateReferenceSystem(code2);
+ constructor = (factory, metadata) ->
factory.createCompoundCRS(metadata, crs1, crs2);
break;
}
- /*
----------------------------------------------------------------------
- * ENGINEERING CRS
- *
---------------------------------------------------------------------- */
- case "engineering": {
- CoordinateSystem cs =
owner.createCoordinateSystem(getString(code, result, 8));
- EngineeringDatum datum =
owner.createEngineeringDatum(getString(code, result, 9));
- DatumEnsemble<EngineeringDatum> ensemble =
wasDatumEnsemble(datum, EngineeringDatum.class);
- if (ensemble != null) datum = null;
- crs =
crsFactory.createEngineeringCRS(createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated), datum, ensemble, cs);
- break;
- }
- /*
----------------------------------------------------------------------
- * PARAMETRIC CRS
- *
---------------------------------------------------------------------- */
- case "parametric": {
- ParametricCS cs = owner.createParametricCS
(getString(code, result, 8));
- ParametricDatum datum =
owner.createParametricDatum(getString(code, result, 9));
- DatumEnsemble<ParametricDatum> ensemble =
wasDatumEnsemble(datum, ParametricDatum.class);
- if (ensemble != null) datum = null;
- crs =
crsFactory.createParametricCRS(createProperties("Coordinate Reference System",
- epsg, name, null, area, scope, remarks,
deprecated), datum, ensemble, cs);
- break;
- }
- /*
----------------------------------------------------------------------
+ /*
──────────────────────────────────────────────────────────────────────
* UNKNOWN CRS
- *
---------------------------------------------------------------------- */
+ *
────────────────────────────────────────────────────────────────────── */
default: {
throw new
FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type));
}
}
- returnValue = ensureSingleton(crs, returnValue, code);
- if (result.isClosed()) {
- return returnValue;
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ final Map<String,Object> properties;
+ ensureNoCycle(CoordinateReferenceSystem.class, epsg);
+ try {
+ properties = createProperties("Coordinate Reference
System",
+ epsg, name, null, area, scope, remarks,
deprecated);
+ } finally {
+ endOfCycleCheck(CoordinateReferenceSystem.class, epsg);
}
+ final CoordinateReferenceSystem crs =
constructor.create(owner.crsFactory, properties);
+ returnValue = ensureSingleton(crs, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(CoordinateReferenceSystem.class, code,
exception);
@@ -1709,7 +1790,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* The real type is resolved by inspection of the {@link
#createDatum(String)} return value.
*
* <h4>Design restriction</h4>
- * We cannot resolve the type with a private field which would be set by
{@code #createDatumEnsemble(…)}
+ * We cannot resolve the type with a private field which would be set by
{@link #createDatumEnsemble(String)}
* because that method will not be invoked if the datum is fetched from
the cache.
*
* @param <D> compile-time value of {@code memberType}.
@@ -1782,48 +1863,51 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final String scope = getOptionalString (result, 9);
final String remarks = getOptionalString (result, 10);
final boolean deprecated = getOptionalBoolean (result, 11);
- @SuppressWarnings("LocalVariableHidesMemberVariable")
- Map<String,Object> properties = createProperties("Datum",
- epsg, name, null, area, scope, remarks, deprecated);
- properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
- properties.put(Datum.ANCHOR_EPOCH_KEY, epoch);
- properties.put(Datum.PUBLICATION_DATE_KEY, publish);
- properties.put(Datum.CONVENTIONAL_RS_KEY,
createConventionalRS(getOptionalInteger(result, 15)));
+ final Integer convRSCode = getOptionalInteger (result, 15);
+ /*
+ * Do not invoke `createProperties` now, even if we have all
required information,
+ * because the `properties` map will be overwritten by calls
to `createEllipsoid`
+ * and similar methods. Instead, remember the constructor to
invoke later.
+ */
+ final FactoryCall<DatumFactory, ? extends Datum> constructor;
+ BursaWolfParameters[] param = null;
/*
* The following switch statement should have a case for all
"epsg_datum_kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in
this Java code are in lower cases.
*/
- final DatumFactory datumFactory = owner.datumFactory;
- final Datum datum;
switch (type.toLowerCase(Locale.US)) {
- /*
- * The "geodetic" case invokes createProperties(…)
indirectly through calls to
- * createEllipsoid(String) and
createPrimeMeridian(String), so we must protect
- * the properties map from changes.
- */
case "dynamic geodetic":
case "geodetic": {
- properties = new HashMap<>(properties); //
Protect from changes
- final Ellipsoid ellipsoid = owner.createEllipsoid
(getString(code, result, 12));
- final PrimeMeridian meridian =
owner.createPrimeMeridian(getString(code, result, 13));
- final BursaWolfParameters[] param =
createBursaWolfParameters(meridian, epsg);
- if (param != null) {
-
properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, param);
- }
- if (dynamic != null) {
- datum =
datumFactory.createGeodeticDatum(properties, ellipsoid, meridian, dynamic);
- } else {
- datum =
datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+ final Ellipsoid ellipsoid;
+ final PrimeMeridian meridian;
+ ensureNoCycle(GeodeticDatum.class, epsg);
+ try {
+ String ellipsoidCode = getString(code, result, 12);
+ String meridianCode = getString(code, result, 13);
+ ellipsoid = owner.createEllipsoid(ellipsoidCode);
// Do not inline the `getString(…)` calls.
+ meridian =
owner.createPrimeMeridian(meridianCode);
+ param = createBursaWolfParameters(meridian,
epsg);
+ } finally {
+ endOfCycleCheck(GeodeticDatum.class, epsg);
}
+ constructor = (factory, metadata) -> {
+ if (dynamic != null) {
+ return factory.createGeodeticDatum(metadata,
ellipsoid, meridian, dynamic);
+ } else {
+ return factory.createGeodeticDatum(metadata,
ellipsoid, meridian);
+ }
+ };
break;
}
case "vertical": {
final RealizationMethod method =
getRealizationMethod(getOptionalInteger(result, 14));
- if (dynamic != null) {
- datum =
datumFactory.createVerticalDatum(properties, method, dynamic);
- } else {
- datum =
datumFactory.createVerticalDatum(properties, method);
- }
+ constructor = (factory, metadata) -> {
+ if (dynamic != null) {
+ return factory.createVerticalDatum(metadata,
method, dynamic);
+ } else {
+ return factory.createVerticalDatum(metadata,
method);
+ }
+ };
break;
}
/*
@@ -1840,33 +1924,40 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
} catch (RuntimeException e) {
throw new
FactoryDataException(resources().getString(Resources.Keys.DatumOriginShallBeDate),
e);
}
- datum = datumFactory.createTemporalDatum(properties,
originDate);
+ constructor = (factory, metadata) ->
factory.createTemporalDatum(metadata, originDate);
break;
}
/*
- * Straightforward case.
+ * Straightforward cases.
*/
- case "engineering": {
- datum =
datumFactory.createEngineeringDatum(properties);
- break;
- }
- case "parametric": {
- datum = datumFactory.createParametricDatum(properties);
- break;
- }
- case "ensemble": {
- properties = new HashMap<>(properties); //
Protect from changes
- datum =
DefaultDatumEnsemble.castOrCopy(createDatumEnsemble(epsg, properties));
- break;
- }
- default: {
- throw new
FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type));
- }
+ case "engineering": constructor =
DatumFactory::createEngineeringDatum; break;
+ case "parametric": constructor =
DatumFactory::createParametricDatum; break;
+ case "ensemble": constructor =
createDatumEnsemble(epsg); break;
+ default: throw new
FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type));
}
- returnValue = ensureSingleton(datum, returnValue, code);
- if (result.isClosed()) {
- break; // Because of the recursive call
done by createBursaWolfParameters(…).
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ final Map<String,Object> properties;
+ final IdentifiedObject conventionalRS;
+ ensureNoCycle(Datum.class, epsg);
+ try {
+ conventionalRS = createConventionalRS(convRSCode); //
Must be before `createProperties(…)`.
+ properties = createProperties("Datum", epsg, name, null,
area, scope, remarks, deprecated);
+ } finally {
+ endOfCycleCheck(Datum.class, epsg);
}
+ properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
+ properties.put(Datum.ANCHOR_EPOCH_KEY, epoch);
+ properties.put(Datum.PUBLICATION_DATE_KEY, publish);
+ properties.put(Datum.CONVENTIONAL_RS_KEY, conventionalRS);
+ properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, param);
+ properties.values().removeIf(Objects::isNull);
+ final Datum datum = constructor.create(owner.datumFactory,
properties);
+ returnValue = ensureSingleton(datum, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(Datum.class, code, exception);
@@ -1878,19 +1969,20 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
}
/**
- * Creates an arbitrary datum ensemble from a code.
+ * Creates an arbitrary datum ensemble from a code. The datum ensemble is
returned as a lambda function
+ * because the metadata need to be provided by the caller, but only after
this method fetched the members.
*
* @param code value allocated by EPSG.
* @param properties properties to assign to the datum ensemble.
- * @return the datum ensemble for the given code, or {@code null} if not
found.
+ * @return provider of the datum ensemble for the given code.
*
* @see #createDatum(String)
* @see #createDatumEnsemble(String)
*/
- private DatumEnsemble<?> createDatumEnsemble(final Integer code, final
Map<String,Object> properties)
+ private FactoryCall<DatumFactory, DefaultDatumEnsemble<?>>
createDatumEnsemble(final Integer code)
throws SQLException, FactoryException
{
- double accuracy = Double.NaN;
+ double max = Double.NaN;
try (ResultSet result = executeQuery("DatumEnsemble",
"SELECT ENSEMBLE_ACCURACY" +
" FROM \"DatumEnsemble\"" +
@@ -1899,11 +1991,12 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
// Should have exactly one value. The loop is a paranoiac safety.
while (result.next()) {
final double value = getDouble(code, result, 1);
- if (Double.isNaN(accuracy) || value > accuracy) {
- accuracy = value;
+ if (Double.isNaN(max) || value > max) {
+ max = value;
}
}
}
+ final var accuracy = PositionalAccuracyConstant.ensemble(max);
final var members = new ArrayList<Datum>();
try (ResultSet result = executeQuery("DatumEnsembleMember",
"SELECT DATUM_CODE" +
@@ -1915,7 +2008,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
members.add(owner.createDatum(getString(code, result, 1)));
}
}
- return owner.datumFactory.createDatumEnsemble(properties, members,
PositionalAccuracyConstant.ensemble(accuracy));
+ return (factory, metadata) ->
DefaultDatumEnsemble.castOrCopy(factory.createDatumEnsemble(metadata, members,
accuracy));
}
/**
@@ -1946,10 +2039,15 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final String name = getString (code, result, 2);
final String remarks = getOptionalString (result, 3);
final boolean deprecated = getOptionalBoolean (result, 4);
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may
be closed.
+ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
Map<String,Object> properties =
createProperties("ConventionalRS",
epsg, name, null, null, null, remarks, deprecated);
returnValue = ensureSingleton(new
AbstractIdentifiedObject(properties), returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…)
for explanation.
}
}
localCache.put(cacheKey, returnValue);
@@ -2031,13 +2129,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
int count = 0;
for (int i=0; i<size; i++) {
final BursaWolfInfo info = bwInfos.get(i);
- final GeodeticDatum datum;
- ensureNoCycle(BursaWolfParameters.class, code); // See comment
at the begining of this method.
- try {
- datum = owner.createGeodeticDatum(String.valueOf(info.target));
- } finally {
- endOfRecursion(BursaWolfParameters.class, code);
- }
+ final GeodeticDatum datum =
owner.createGeodeticDatum(String.valueOf(info.target));
/*
* Accept only Bursa-Wolf parameters between datum that use the
same prime meridian.
* This is for avoiding ambiguity about whether longitude rotation
should be applied
@@ -2131,16 +2223,21 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final String remarks = getOptionalString (result,
7);
final boolean deprecated = getOptionalBoolean(result,
8);
final Unit<Length> unit =
owner.createUnit(unitCode).asType(Length.class);
+ final boolean useSemiMinor =
Double.isNaN(inverseFlattening);
+ if (useSemiMinor && Double.isNaN(semiMinorAxis)) {
+ // Both are null, which is not allowed.
+ final String column =
result.getMetaData().getColumnName(3);
+ throw new
FactoryDataException(error().getString(Errors.Keys.NullValueInTable_3, code,
column));
+ }
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties =
createProperties("Ellipsoid",
epsg, name, null, null, null, remarks, deprecated);
final Ellipsoid ellipsoid;
- if (Double.isNaN(inverseFlattening)) {
- if (Double.isNaN(semiMinorAxis)) {
- // Both are null, which is not allowed.
- final String column =
result.getMetaData().getColumnName(3);
- throw new
FactoryDataException(error().getString(Errors.Keys.NullValueInTable_3, code,
column));
- }
+ if (useSemiMinor) {
// We only have semiMinorAxis defined. It is OK
ellipsoid = owner.datumFactory.createEllipsoid(properties,
semiMajorAxis, semiMinorAxis, unit);
} else {
@@ -2156,6 +2253,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
ellipsoid =
owner.datumFactory.createFlattenedSphere(properties, semiMajorAxis,
inverseFlattening, unit);
}
returnValue = ensureSingleton(ellipsoid, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(Ellipsoid.class, code, exception);
@@ -2214,10 +2312,15 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final String remarks = getOptionalString (result, 5);
final boolean deprecated = getOptionalBoolean(result, 6);
final Unit<Angle> unit =
owner.createUnit(unitCode).asType(Angle.class);
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
final PrimeMeridian primeMeridian =
owner.datumFactory.createPrimeMeridian(
createProperties("Prime Meridian", epsg, name, null,
null, null, remarks, deprecated),
longitude, unit);
returnValue = ensureSingleton(primeMeridian, returnValue,
code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(PrimeMeridian.class, code, exception);
@@ -2264,24 +2367,34 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
throws NoSuchAuthorityCodeException, FactoryException
{
ArgumentChecks.ensureNonNull("code", code);
- Extent returnValue = null;
+ DefaultExtent returnValue = null;
+ final var deferred = new ArrayList<Map.Entry<DefaultVerticalExtent,
Integer>>();
try (ResultSet result = executeQuery("Extent", "EXTENT_CODE",
"EXTENT_NAME",
"SELECT EXTENT_DESCRIPTION," +
" BBOX_SOUTH_BOUND_LAT," +
" BBOX_NORTH_BOUND_LAT," +
" BBOX_WEST_BOUND_LON," +
- " BBOX_EAST_BOUND_LON" +
+ " BBOX_EAST_BOUND_LON," +
+ " VERTICAL_EXTENT_MIN," +
+ " VERTICAL_EXTENT_MAX," +
+ " VERTICAL_EXTENT_CRS_CODE," +
+ " TEMPORAL_EXTENT_BEGIN," +
+ " TEMPORAL_EXTENT_END" +
" FROM \"Extent\"" +
" WHERE EXTENT_CODE = ?", code))
{
while (result.next()) {
final String description = getOptionalString(result, 1);
- double ymin = getOptionalDouble(result, 2);
- double ymax = getOptionalDouble(result, 3);
- double xmin = getOptionalDouble(result, 4);
- double xmax = getOptionalDouble(result, 5);
+ double ymin = getOptionalDouble (result, 2);
+ double ymax = getOptionalDouble (result, 3);
+ double xmin = getOptionalDouble (result, 4);
+ double xmax = getOptionalDouble (result, 5);
+ double zmin = getOptionalDouble (result, 6);
+ double zmax = getOptionalDouble (result, 7);
+ Temporal tmin = getOptionalTemporal(result, 9,
"createExtent");
+ Temporal tmax = getOptionalTemporal(result, 10,
"createExtent");
DefaultGeographicBoundingBox bbox = null;
- if (!Double.isNaN(ymin) || !Double.isNaN(ymax) ||
!Double.isNaN(xmin) || !Double.isNaN(xmax)) {
+ if (!(Double.isNaN(ymin) && Double.isNaN(ymax) &&
Double.isNaN(xmin) && Double.isNaN(xmax))) {
/*
* Fix an error found in EPSG:3790 New Zealand - South
Island - Mount Pleasant mc
* for older database (this error is fixed in EPSG
database 8.2).
@@ -2296,18 +2409,37 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
}
bbox = new DefaultGeographicBoundingBox(xmin, xmax, ymin,
ymax);
}
- if (description != null || bbox != null) {
- var extent = new DefaultExtent(description, bbox, null,
null);
- extent.transitionTo(DefaultExtent.State.FINAL);
+ DefaultVerticalExtent vertical = null;
+ if (!(Double.isNaN(zmin) && Double.isNaN(zmax))) {
+ vertical = new DefaultVerticalExtent(zmin, zmax, null);
+ Integer crs = getOptionalInteger(result, 8);
+ if (crs != null && !safetyGuard.containsKey(crs)) {
+ deferred.add(Map.entry(vertical, crs));
+ }
+ }
+ DefaultTemporalExtent temporal = null;
+ if (tmin != null || tmax != null) {
+ temporal = new DefaultTemporalExtent(tmin, tmax);
+ }
+ var extent = new DefaultExtent(description, bbox, vertical,
temporal);
+ if (!extent.isEmpty()) {
returnValue = ensureSingleton(extent, returnValue, code);
}
}
} catch (SQLException exception) {
throw databaseFailure(Extent.class, code, exception);
}
+ /*
+ * Resolve CRS only after we finished the loop, because there is a
risk of recursive call,
+ * which would have closed the `ResultSet` for creating a new one.
+ */
+ for (Map.Entry<DefaultVerticalExtent, Integer> entry : deferred) {
+
entry.getKey().setVerticalCRS(owner.createVerticalCRS(entry.getValue().toString()));
+ }
if (returnValue == null) {
throw noSuchAuthorityCode(Extent.class, code);
}
+ returnValue.transitionTo(DefaultExtent.State.FINAL);
return returnValue;
}
@@ -2363,6 +2495,10 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final String remarks = getOptionalString (result, 5);
final boolean deprecated = getOptionalBoolean(result, 6);
final CoordinateSystemAxis[] axes =
createCoordinateSystemAxes(epsg, dimension);
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties =
createProperties("Coordinate System",
epsg, name, null, null, null, remarks, deprecated);
// Must be after axes.
@@ -2447,6 +2583,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
throw new
FactoryDataException(resources().getString(Resources.Keys.UnexpectedDimensionForCS_1,
type));
}
returnValue = ensureSingleton(cs, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(CoordinateSystem.class, code, exception);
@@ -2546,10 +2683,15 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
throw new
FactoryDataException(exception.getLocalizedMessage(), exception);
}
final AxisName an = getAxisName(nameCode);
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
final CoordinateSystemAxis axis =
owner.csFactory.createCoordinateSystemAxis(
createProperties("Coordinate Axis", epsg, an.name,
an.description, null, null, an.remarks, false),
abbreviation, direction, owner.createUnit(unit));
returnValue = ensureSingleton(axis, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(CoordinateSystemAxis.class, code, exception);
@@ -2836,12 +2978,17 @@ next: while (r.next()) {
case 1: valueDomain =
MeasurementRange.create(Double.NEGATIVE_INFINITY, false,
Double.POSITIVE_INFINITY, false,
CollectionsExt.first(units)); break;
}
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties =
createProperties("Coordinate_Operation Parameter",
epsg, name, null, null, null, isReversible,
deprecated);
properties.put(Identifier.DESCRIPTION_KEY, description);
final var descriptor = new
DefaultParameterDescriptor<>(properties, 1, 1, type, valueDomain, null, null);
returnValue = ensureSingleton(descriptor, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(OperationMethod.class, code, exception);
@@ -3000,6 +3147,10 @@ next: while (r.next()) {
final String remarks = getOptionalString (result, 3);
final boolean deprecated = getOptionalBoolean(result, 4);
final ParameterDescriptor<?>[] descriptors =
createParameterDescriptors(epsg);
+ /*
+ * Map of properties should be populated only after we
extracted all
+ * information needed from the `ResultSet`, because it may be
closed.
+ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties =
createProperties("Coordinate_Operation Method",
epsg, name, null, null, null, remarks, deprecated);
@@ -3009,6 +3160,7 @@ next: while (r.next()) {
final var params = new
DefaultParameterDescriptorGroup(properties, 1, 1, descriptors);
final var method = new DefaultOperationMethod(properties,
params);
returnValue = ensureSingleton(method, returnValue, code);
+ if (result.isClosed()) break; // See createProperties(…) for
explanation.
}
} catch (SQLException exception) {
throw databaseFailure(OperationMethod.class, code, exception);
@@ -3041,7 +3193,6 @@ next: while (r.next()) {
* @throws FactoryException if the object creation failed for some other
reason.
*/
@Override
- @SuppressWarnings("try") // Explicit call to close() on an
auto-closeable resource.
public synchronized CoordinateOperation createCoordinateOperation(final
String code)
throws NoSuchAuthorityCodeException, FactoryException
{
@@ -3152,10 +3303,9 @@ next: while (r.next()) {
operation =
copFactory.createDefiningConversion(opProperties, method, parameters);
} else if (isConcatenated) {
/*
- * Concatenated operation: we need to close the
current result set, because
+ * Concatenated operation: the current `ResulSet` may
be closed, because
* we are going to invoke this method recursively in
the following lines.
*/
- result.close();
opProperties = new HashMap<>(opProperties); //
Because this class uses a shared map.
final var codes = new ArrayList<String>();
try (ResultSet cr = executeQuery("Coordinate_Operation
Path",
@@ -3175,7 +3325,7 @@ next: while (r.next()) {
operations[i] =
owner.createCoordinateOperation(codes.get(i));
}
} finally {
- endOfRecursion(CoordinateOperation.class, epsg);
+ endOfCycleCheck(CoordinateOperation.class, epsg);
}
return
copFactory.createConcatenatedOperation(opProperties, operations);
} else {
@@ -3229,9 +3379,7 @@ next: while (r.next()) {
.createSingleOperation(opProperties,
sourceCRS, targetCRS, null, method, mt);
}
returnValue = ensureSingleton(operation, returnValue,
code);
- if (result.isClosed()) {
- return returnValue;
- }
+ if (result.isClosed()) break; // See createProperties(…)
for explanation.
}
}
} catch (SQLException exception) {
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index 3c9383dff0..ca2979436d 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -376,13 +376,12 @@ public class SQLTranslator implements
UnaryOperator<String> {
final var missingColumns = new HashMap<String, String>();
final var mayRenameColumns = new HashMap<String, String>();
final var brokenTargetCols = new HashSet<String>();
- boolean isExtentTableFound = false;
tableIndex = 0;
check: for (;;) {
String table;
boolean isUsage = false; // "Usage" is a new table in EPSG
version 19.
boolean isArea = false; // "Area" was a table name used in
EPSG version 9.
- boolean isExtent = false; // "Extent" is the new name for "Area"
in EPSG version 10.
+ boolean mayReuse = false; // If true, do not clear the maps if
the table was not found.
switch (tableIndex++) {
case 0: {
table = "Coordinate Axis";
@@ -396,27 +395,34 @@ check: for (;;) {
}
case 2: {
table = "Datum";
- missingColumns.put("ANCHOR_EPOCH", "DOUBLE
PRECISION");
- missingColumns.put("FRAME_REFERENCE_EPOCH", "DOUBLE
PRECISION");
- missingColumns.put("REALIZATION_METHOD_CODE", "INTEGER");
- missingColumns.put("CONVENTIONAL_RS_CODE", "INTEGER");
- mayRenameColumns.put("PUBLICATION_DATE",
"REALIZATION_EPOCH"); // EPSG version 10 → version 9.
+ mayRenameColumns.put("PUBLICATION_DATE",
"REALIZATION_EPOCH"); // EPSG version 10 → version 9.
+ missingColumns .put("ANCHOR_EPOCH", "DOUBLE
PRECISION");
+ missingColumns .put("FRAME_REFERENCE_EPOCH", "DOUBLE
PRECISION");
+ missingColumns .put("REALIZATION_METHOD_CODE", "INTEGER");
+ missingColumns .put("CONVENTIONAL_RS_CODE", "INTEGER");
break;
}
- case 4:
- if (isExtentTableFound) continue;
- isArea = true;
- // Fallthrough for testing "Area" if and only if "Extent"
has not been found.
case 3: {
- isExtent = !isArea;
- table = isExtent ? "Extent" : "Area";
// "Area" in 9, "Extent" in 10.
- mayRenameColumns.put("EXTENT_CODE", "AREA_CODE");
// EPSG version 10 → version 9.
- mayRenameColumns.put("EXTENT_NAME", "AREA_NAME");
- mayRenameColumns.put("EXTENT_DESCRIPTION",
"AREA_OF_USE");
- mayRenameColumns.put("BBOX_SOUTH_BOUND_LAT",
"AREA_SOUTH_BOUND_LAT");
- mayRenameColumns.put("BBOX_NORTH_BOUND_LAT",
"AREA_NORTH_BOUND_LAT");
- mayRenameColumns.put("BBOX_WEST_BOUND_LON",
"AREA_WEST_BOUND_LON");
- mayRenameColumns.put("BBOX_EAST_BOUND_LON",
"AREA_EAST_BOUND_LON");
+ table = "Extent";
// "Area" in 9, "Extent" in 10.
+ mayRenameColumns.put("EXTENT_CODE",
"AREA_CODE"); // EPSG version 10 → version 9.
+ mayRenameColumns.put("EXTENT_NAME",
"AREA_NAME");
+ mayRenameColumns.put("EXTENT_DESCRIPTION",
"AREA_OF_USE");
+ mayRenameColumns.put("BBOX_SOUTH_BOUND_LAT",
"AREA_SOUTH_BOUND_LAT");
+ mayRenameColumns.put("BBOX_NORTH_BOUND_LAT",
"AREA_NORTH_BOUND_LAT");
+ mayRenameColumns.put("BBOX_WEST_BOUND_LON",
"AREA_WEST_BOUND_LON");
+ mayRenameColumns.put("BBOX_EAST_BOUND_LON",
"AREA_EAST_BOUND_LON");
+ missingColumns .put("VERTICAL_EXTENT_MIN", "DOUBLE
PRECISION");
+ missingColumns .put("VERTICAL_EXTENT_MAX", "DOUBLE
PRECISION");
+ missingColumns .put("VERTICAL_EXTENT_CRS_CODE",
"INTEGER");
+ missingColumns .put("TEMPORAL_EXTENT_BEGIN",
"VARCHAR(254)");
+ missingColumns .put("TEMPORAL_EXTENT_END",
"VARCHAR(254)");
+ mayReuse = true;
+ break;
+ }
+ case 4: {
+ if (mayRenameColumns.isEmpty()) continue; // "Extent"
table has been found.
+ isArea = true;
+ table = "Area";
break;
}
case 5: {
@@ -425,7 +431,7 @@ check: for (;;) {
break;
}
case 6: {
- if (isUsageTableFound) continue; // The check of
`ENUMERATION_COLUMN` is already done.
+ if (isUsageTableFound) break check; // The check of
`ENUMERATION_COLUMN` is already done.
table = "Alias"; // For checking the
type of the `ENUMERATION_COLUMN`.
break;
}
@@ -464,18 +470,20 @@ check: for (;;) {
}
if (isTableFound) {
isUsageTableFound |= isUsage;
- isExtentTableFound |= isExtent;
+ if (isArea) {
+ tableRewording.put("Extent", "Area");
+ }
missingColumns.forEach((column, type) -> {
columnRenaming.put(column, "CAST(NULL AS " + type + ") AS
" + column);
});
mayRenameColumns.values().removeAll(brokenTargetCols); // For
renaming only when the old name has been found.
columnRenaming.putAll(mayRenameColumns);
+ mayReuse = false;
+ }
+ if (!mayReuse) {
mayRenameColumns.clear();
brokenTargetCols.clear();
missingColumns.clear();
- if (isArea) {
- tableRewording.put("Extent", "Area");
- }
}
}
tableRewording = Map.copyOf(tableRewording);
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Cache.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Cache.java
index 5a99b2ddf9..b30d79d546 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Cache.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/Cache.java
@@ -575,7 +575,7 @@ public class Cache<K,V> extends AbstractMap<K,V> implements
ConcurrentMap<K,V> {
*/
@Override
public void replaceAll(final BiFunction<? super K, ? super V, ? extends V>
remapping) {
- final ReplaceAdapter adapter = new ReplaceAdapter(remapping);
+ final var adapter = new ReplaceAdapter(remapping);
map.replaceAll(adapter);
Deferred.notifyChanges(this, adapter.changes);
}
@@ -596,7 +596,7 @@ public class Cache<K,V> extends AbstractMap<K,V> implements
ConcurrentMap<K,V> {
*/
@Override
public V computeIfPresent(final K key, final BiFunction<? super K, ? super
V, ? extends V> remapping) {
- final ReplaceAdapter adapter = new ReplaceAdapter(remapping);
+ final var adapter = new ReplaceAdapter(remapping);
final Object value = map.computeIfPresent(key, adapter);
Deferred.notifyChanges(this, adapter.changes);
return valueOf(value);
@@ -620,7 +620,7 @@ public class Cache<K,V> extends AbstractMap<K,V> implements
ConcurrentMap<K,V> {
*/
@Override
public V compute(final K key, final BiFunction<? super K, ? super V, ?
extends V> remapping) {
- final ReplaceAdapter adapter = new ReplaceAdapter(remapping);
+ final var adapter = new ReplaceAdapter(remapping);
final Object value = map.compute(key, adapter);
Deferred.notifyChanges(this, adapter.changes);
return valueOf(value);
@@ -811,7 +811,7 @@ public class Cache<K,V> extends AbstractMap<K,V> implements
ConcurrentMap<K,V> {
}
if (value instanceof Reference<?>) {
@SuppressWarnings("unchecked")
- final Reference<V> ref = (Reference<V>) value;
+ final var ref = (Reference<V>) value;
final V result = ref.get();
if (result != null && map.replace(key, ref, result)) {
ref.clear(); // Prevents the reference
from being enqueued.
@@ -896,7 +896,7 @@ public class Cache<K,V> extends AbstractMap<K,V> implements
ConcurrentMap<K,V> {
break;
}
@SuppressWarnings("unchecked")
- final Reference<V> ref = (Reference<V>) value;
+ final var ref = (Reference<V>) value;
final V result = ref.get();
if (result != null) {
/*
@@ -1094,6 +1094,7 @@ public class Cache<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V> {
* This method should be invoked only from another thread than the one
doing the computation.
*/
@Override
+ @SuppressWarnings("LockAcquiredButNotSafelyReleased")
public V get() {
if (lock.isHeldByCurrentThread()) {
return null;