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 ad0646c8a4 More type-safe way to specify the EPSG tables on which
queries are executed.
ad0646c8a4 is described below
commit ad0646c8a4ee5a958a9ee8bb11c4d6395fc8fefc
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Aug 28 15:57:32 2025 +0200
More type-safe way to specify the EPSG tables on which queries are executed.
---
.../factory/MultiAuthoritiesFactory.java | 10 +-
.../referencing/factory/sql/AuthorityCodes.java | 40 +--
.../referencing/factory/sql/EPSGCodeFinder.java | 109 ++++----
.../referencing/factory/sql/EPSGDataAccess.java | 257 +++++++++---------
.../sis/referencing/factory/sql/TableInfo.java | 299 ++++++++++++---------
.../sis/referencing/factory/sql/TableInfoTest.java | 14 +-
.../apache/sis/storage/netcdf/base/AxisType.java | 2 +-
7 files changed, 397 insertions(+), 334 deletions(-)
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 15708c50f8..9e819db570 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
@@ -1511,7 +1511,9 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
* but instead stores information for later execution.
*/
private static final class Deferred extends
AuthorityFactoryProxy<CoordinateOperationAuthorityFactory> {
- Deferred() {super(CoordinateOperationAuthorityFactory.class,
AuthorityFactoryIdentifier.Type.OPERATION);}
+ Deferred() {
+ super(CoordinateOperationAuthorityFactory.class,
AuthorityFactoryIdentifier.Type.OPERATION);
+ }
/** The authority code saved by the {@code createFromAPI(…)} method. */
String code;
@@ -1791,8 +1793,8 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
@Override
final Set<IdentifiedObject> createFromCodes(final IdentifiedObject
object) throws FactoryException {
if (finders == null) try {
- final ArrayList<IdentifiedObjectFinder> list = new
ArrayList<>();
- final Map<AuthorityFactory,Boolean> unique = new
IdentityHashMap<>();
+ final var list = new ArrayList<IdentifiedObjectFinder>();
+ final var unique = new
IdentityHashMap<AuthorityFactory,Boolean>();
final Iterator<AuthorityFactory> it =
((MultiAuthoritiesFactory) factory).getAllFactories();
while (it.hasNext()) {
final AuthorityFactory candidate = it.next();
@@ -1809,7 +1811,7 @@ public class MultiAuthoritiesFactory extends
GeodeticAuthorityFactory implements
} catch (BackingStoreException e) {
throw e.unwrapOrRethrow(FactoryException.class);
}
- final Set<IdentifiedObject> found = new LinkedHashSet<>();
+ final var found = new LinkedHashSet<IdentifiedObject>();
for (final IdentifiedObjectFinder finder : finders) {
found.addAll(finder.find(object));
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
index 2b26f07d70..afa766de0e 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
@@ -66,6 +66,11 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
*/
private static final int ALL_CODES = 0, NAME_FOR_CODE = 1, CODES_FOR_NAME
= 2;
+ /**
+ * Number of queries stored in the {@link #sql} and {@link #statements}
arrays.
+ */
+ private static final int NUM_QUERIES = 3;
+
/**
* The factory which is the owner of this map. One purpose of this field
is to prevent
* garbage collection of that factory as long as this map is in use. This
is required
@@ -125,9 +130,8 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
*/
AuthorityCodes(final TableInfo table, final Class<?> type, final
EPSGDataAccess factory) throws SQLException {
this.factory = factory;
- final int count = (table.nameColumn != null) ? 3 : 1;
- sql = new String[count];
- statements = new Statement[count];
+ sql = new String[NUM_QUERIES];
+ statements = new Statement[NUM_QUERIES];
/*
* Build the SQL query for fetching the codes of all object. It is of
the form:
*
@@ -150,7 +154,7 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
*
* SELECT code FROM table WHERE name LIKE ? AND DEPRECATED=FALSE
ORDER BY code;
*/
- if (count > CODES_FOR_NAME) {
+ if (NUM_QUERIES > CODES_FOR_NAME) {
sql[CODES_FOR_NAME] = buffer.insert(conditionStart,
table.nameColumn + " LIKE ? AND ").toString();
/*
* Workaround for Derby bug. See
`SQLUtilities.filterFalsePositive(…)`.
@@ -165,12 +169,12 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
*
* SELECT name FROM table WHERE code = ?
*/
- if (count > NAME_FOR_CODE) {
+ if (NUM_QUERIES > NAME_FOR_CODE) {
buffer.setLength(conditionStart);
buffer.replace(columnNameStart, columnNameEnd, table.nameColumn);
sql[NAME_FOR_CODE] = buffer.append(table.codeColumn).append(" =
?").toString();
}
- for (int i=0; i<count; i++) {
+ for (int i=0; i<NUM_QUERIES; i++) {
sql[i] = factory.translator.apply(sql[i]);
}
}
@@ -201,21 +205,19 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
* Puts codes associated to the given name in the given collection.
*
* @param pattern the {@code LIKE} pattern of the name to search.
- * @param name the original name (workaround for Derby bug).
+ * @param name the original name. This is a temporary workaround for
a Derby bug (see {@code filterFalsePositive(…)}).
* @param addTo the collection where to add the codes.
* @throws SQLException if an error occurred while querying the database.
*/
final void findCodesFromName(final String pattern, final String name,
final Collection<Integer> addTo) throws SQLException {
- if (statements.length > CODES_FOR_NAME) {
- synchronized (factory) {
- final PreparedStatement statement =
prepareStatement(CODES_FOR_NAME);
- statement.setString(1, pattern);
- try (ResultSet result = statement.executeQuery()) {
- while (result.next()) {
- final int code = result.getInt(1);
- if (!result.wasNull() &&
SQLUtilities.filterFalsePositive(name, result.getString(2))) {
- addTo.add(code);
- }
+ synchronized (factory) {
+ final PreparedStatement statement =
prepareStatement(CODES_FOR_NAME);
+ statement.setString(1, pattern);
+ try (ResultSet result = statement.executeQuery()) {
+ while (result.next()) {
+ final int code = result.getInt(1);
+ if (!result.wasNull() &&
SQLUtilities.filterFalsePositive(name, result.getString(2))) {
+ addTo.add(code);
}
}
}
@@ -317,7 +319,7 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
*/
@Override
public String get(final Object code) {
- if (code != null && statements.length > NAME_FOR_CODE) {
+ if (code != null) {
final int n;
if (code instanceof Number) {
n = ((Number) code).intValue();
@@ -394,7 +396,7 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
String size = null;
synchronized (factory) {
if (codes != null) {
- size = "size " + (results != null ? "≥ " : "= ") +
codes.size();
+ size = "size" + (results != null ? " ≥ " : " = ") +
codes.size();
}
}
return Strings.toString(getClass(), "type", type.getSimpleName(),
null, size);
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 0c0df29970..2b51586929 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
@@ -47,6 +47,7 @@ import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Exceptions;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.privy.Strings;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.privy.CollectionsExt;
import org.apache.sis.util.collection.BackingStoreException;
@@ -509,7 +510,7 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
}
}
}
- dao.sort(source.table, addTo,
Integer::intValue).ifPresent((sorted) -> {
+ dao.sort(source, addTo, Integer::intValue).ifPresent((sorted) -> {
addTo.clear();
addTo.addAll(JDK16.toList(sorted.mapToObj(Integer::valueOf)));
});
@@ -592,9 +593,9 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
*/
@Override
protected Iterable<String> getCodeCandidates(final IdentifiedObject
object) throws FactoryException {
- for (final TableInfo table : TableInfo.EPSG) {
- if (table.type.isInstance(object)) try {
- return new CodeCandidates(object, table);
+ for (final TableInfo source : TableInfo.values()) {
+ if (source.isCandidate() && source.type.isInstance(object)) try {
+ return new CodeCandidates(object, source);
} catch (SQLException exception) {
throw databaseFailure(exception);
}
@@ -610,22 +611,25 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
/** The object to search. */
private final IdentifiedObject object;
+ /** Workaround for a Derby bug (see {@code filterFalsePositive(…)}). */
+ private String name;
+
+ /** {@code LIKE} Pattern of the name of the object to search. */
+ private String namePattern;
+
/** Information about the tables of the object to search. */
private final TableInfo source;
/** Cache of codes found so far. */
private final Set<Integer> codes;
- /** Whether to include the codes of all objects, even the deprecated
ones. */
- private boolean includeAll;
+ /** Snapshot of the search domain as it was at collection construction
time. */
+ private final Domain domain;
- /** Whether to use only identifiers, name and aliases in the search. */
- private boolean easySearch;
+ /** Whether to try to easy search methods before the expansive method.
*/
+ private final boolean optimize;
- /**
- * Algorithm used for filling the {@link #codes} collection so far.
- * 0 = identifiers, 1 = name, 2 = aliases, 3 = search based on
properties.
- */
+ /** Sequential number of the algorithm used for filling the {@link
#codes} collection so far. */
private byte searchMethod;
/**
@@ -640,22 +644,16 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
CodeCandidates(final IdentifiedObject object, final TableInfo source)
throws SQLException, FactoryException {
this.object = object;
this.source = source;
+ this.domain = getSearchDomain();
this.codes = new LinkedHashSet<>();
- switch (getSearchDomain()) {
- case DECLARATION: easySearch = true; break;
- case ALL_DATASET: includeAll = true; break;
- case EXHAUSTIVE_VALID_DATASET: {
- // Skip the search methods based on identifiers, name or
aliases.
- searchCodesFromProperties(object, false, codes);
- searchMethod = 3;
- return;
- }
- }
- for (final Identifier id : object.getIdentifiers()) {
- if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) try {
- codes.add(Integer.valueOf(id.getCode()));
- } catch (NumberFormatException exception) {
- Logging.ignorableException(EPSGDataAccess.LOGGER,
IdentifiedObjectFinder.class, "find", exception);
+ optimize = (domain != Domain.EXHAUSTIVE_VALID_DATASET);
+ if (optimize) {
+ for (final Identifier id : object.getIdentifiers()) {
+ if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace()))
try {
+ codes.add(Integer.valueOf(id.getCode()));
+ } catch (NumberFormatException exception) {
+ Logging.ignorableException(EPSGDataAccess.LOGGER,
IdentifiedObjectFinder.class, "find", exception);
+ }
}
}
if (codes.isEmpty()) {
@@ -675,33 +673,39 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
private boolean fetchMoreCodes(final Collection<Integer> addTo) throws
SQLException, FactoryException {
do {
switch (searchMethod) {
- case 0: findCodesFromName(false, addTo); break; //
Fetch codes for the name.
- case 1: findCodesFromName(true, addTo); break; //
Fetch codes for the aliases.
- case 2: if (easySearch) break; //
Search codes based on object properties.
- searchCodesFromProperties(object, includeAll,
addTo);
- break;
- default: return false;
+ case 0: { // Fetch codes from the name.
+ if (optimize) {
+ name = getName(object);
+ if (name != null) { // Should never be null,
but we are paranoiac.
+ namePattern = dao.toLikePattern(name);
+ dao.findCodesFromName(source,
object.getClass(), namePattern, name, addTo);
+ }
+ }
+ break;
+ }
+ case 1: { // Fetch codes from the aliases.
+ if (optimize) {
+ if (namePattern != null) {
+ dao.findCodesFromAlias(source, namePattern,
name, addTo);
+ }
+ }
+ break;
+ }
+ case 2: { // Search codes based on object properties.
+ if (domain != Domain.DECLARATION) {
+ searchCodesFromProperties(object, domain ==
Domain.ALL_DATASET, addTo);
+ }
+ break;
+ }
+ default: {
+ return false;
+ }
}
searchMethod++;
} while (addTo.isEmpty());
return true;
}
- /**
- * Adds the authority codes of all objects of the given name.
- * Callers should update the {@link #searchMethod} flag accordingly.
- *
- * @param alias whether to search in the alias table rather than the
main name.
- * @param codes the collection where to add the codes that have been
found.
- * @throws SQLException if an error occurred while querying the
database.
- */
- private void findCodesFromName(final boolean alias, final
Collection<Integer> addTo) throws SQLException {
- final String name = getName(object);
- if (name != null) { // Should never be null, but we are
paranoiac.
- dao.findCodesFromName(source.table, object.getClass(), name,
alias, addTo);
- }
- }
-
/**
* Returns additional code candidates which were not yet returned by
the iteration.
* This method uses the next search method which hasn't be tried.
@@ -758,5 +762,14 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
}
};
}
+
+ /**
+ * Returns a string representation for debugging purposes.
+ * The {@code "size"} property may change during the iteration.
+ */
+ @Override
+ public String toString() {
+ return Strings.toString(getClass(), "object", getName(object),
"source", source, "domain", domain, "size", codes.size());
+ }
}
}
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 5158226b41..d39afd31b2 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
@@ -259,7 +259,7 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* </ol>
*
* Since we are not using the shared cache, there is a possibility that
many objects are created for the same code.
- * However, this duplication should not happen often. For example, each
conventional <abbr>RS</abbr> should appears
+ * However, this duplication should not happen often. For example, each
conventional <abbr>RS</abbr> should appear
* in only one datum ensemble created by {@link
#createDatumEnsemble(Integer, Map)}.
*
* <p>Keys are {@link Long} except the keys for naming systems which are
{@link String}.</p>
@@ -267,7 +267,7 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* @see #getAxisName(Integer)
* @see #getRealizationMethod(Integer)
* @see #createConventionalRS(Integer)
- * @see #createProperties(String, Integer, String, CharSequence, String,
String, CharSequence, boolean)
+ * @see #createProperties(TableInfo, Integer, String, CharSequence,
String, String, CharSequence, boolean)
*/
private final Map<Object, Object> localCache = new HashMap<>();
@@ -600,8 +600,8 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
}
}
if (source == null) {
- for (TableInfo c : TableInfo.EPSG) {
- if (c.type.isAssignableFrom(type)) {
+ for (TableInfo c : TableInfo.values()) {
+ if (c.isCandidate() && c.type.isAssignableFrom(type)) {
if (source != null) {
return null; // The specified type is too
generic.
}
@@ -718,28 +718,29 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* Converts <abbr>EPSG</abbr> codes or <abbr>EPSG</abbr> names to the
numerical identifiers (the primary keys).
* This method can be seen as the converse of above {@link
#getDescriptionText(Class, String)} method.
*
- * @param table the table where the code should appear, or {@code null}
for no search by name.
- * @param codes the codes or names to convert to primary keys, as an
array of length 1 or 2.
+ * @param source the table where the code should appear, or {@code null}
for no search by name.
+ * @param codes the codes or names to convert to primary keys, as an
array of length 1 or 2.
* @return the numerical identifiers (i.e. the table primary key values).
* @throws SQLException if an error occurred while querying the database.
* @throws FactoryDataException if code is a name and two distinct
numerical codes match the name.
* @throws NoSuchAuthorityCodeException if code is a name and no numerical
code match the name.
*/
- private int[] toPrimaryKeys(final String table, final String... codes)
throws SQLException, FactoryException {
+ private int[] toPrimaryKeys(final TableInfo source, final String... codes)
throws SQLException, FactoryException {
final int[] primaryKeys = new int[codes.length];
for (int i=0; i<codes.length; i++) {
String code = codes[i];
- if (table != null && !isPrimaryKey(code)) {
+ if (source != null && !isPrimaryKey(code)) {
/*
* The given string is not a numerical code. Search the value
in the database.
* We search first in the table of the query. If the name is
not found there,
* then we will search in the aliases table as a fallback.
*/
final var result = new ArrayList<Integer>();
- findCodesFromName(table, null, code, false, result);
+ final String pattern = toLikePattern(code);
+ findCodesFromName(source, source.type, pattern, code, result);
if (result.isEmpty()) {
// Search in aliases only if no match was found in primary
names.
- findCodesFromName(table, null, code, true, result);
+ findCodesFromAlias(source, pattern, code, result);
}
Integer resolved = null;
for (Integer value : result) {
@@ -767,43 +768,57 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
return primaryKeys;
}
+ /**
+ * Returns the given object name as a pattern which can be used in a
{@code LIKE} clause.
+ * This method does not change the character case for avoiding the need to
use {@code LOWER}
+ * in the <abbr>SQL</abbr> statement (because it may prevent the use of
the database index).
+ */
+ final String toLikePattern(final String name) {
+ return SQLUtilities.toLikePattern(name, false,
translator.wildcardEscape);
+ }
+
/**
* Finds the authority codes for the given name.
*
- * @param table the table where the code should appear.
- * @parma type the type of object to searh, or {@code null} for
inferring from the table.
- * @param name the name to search.
- * @param alias whether to search in the alias table rather than the
main name.
- * @param addTo the collection where to add the codes that have been
found.
+ * @param source information about the table where the code should
appear.
+ * @param type the type of object to search. Should be assignable to
{@code source.type}.
+ * @param pattern the name to search as a pattern that can be used with
{@code LIKE}.
+ * @param name the original name. This is a temporary workaround for
a Derby bug (see {@code filterFalsePositive(…)}).
+ * @param addTo the collection where to add the codes that have been
found.
* @throws SQLException if an error occurred while querying the database.
*/
- final void findCodesFromName(final String table, final Class<?> type,
final String name, final boolean alias, final Collection<Integer> addTo)
+ final void findCodesFromName(final TableInfo source, final Class<?> type,
final String pattern, final String name, final Collection<Integer> addTo)
throws SQLException
{
- final String pattern = SQLUtilities.toLikePattern(name, false,
translator.wildcardEscape);
- if (alias) {
- final PreparedStatement stmt = prepareStatement(
- "AliasKey",
- "SELECT OBJECT_CODE, ALIAS"
- + " FROM \"Alias\""
- + " WHERE OBJECT_TABLE_NAME=? AND ALIAS LIKE ?");
- stmt.setString(1, translator.toActualTableName(table));
- stmt.setString(2, pattern);
- try (ResultSet result = stmt.executeQuery()) {
- while (result.next()) {
- if (SQLUtilities.filterFalsePositive(name,
result.getString(2))) {
- addTo.add(getOptionalInteger(result, 1));
- }
- }
- }
- } else {
- for (final TableInfo source : TableInfo.EPSG) {
- if (table.equals(source.table)) {
- AuthorityCodes codes = getCodeMap(type == null ?
source.type : type, source, false);
- if (codes != null) {
- codes.findCodesFromName(pattern, name, addTo);
- break;
- }
+ AuthorityCodes codes = getCodeMap(type, source, false);
+ if (codes != null) {
+ codes.findCodesFromName(pattern, name, addTo);
+ }
+ }
+
+ /**
+ * Finds the authority codes for the given alias.
+ *
+ * @param source information about the table where the code should
appear.
+ * @param pattern the name to search as a pattern that can be used with
{@code LIKE}.
+ * @param name the original name. This is a temporary workaround for
a Derby bug (see {@code filterFalsePositive(…)}).
+ * @param addTo the collection where to add the codes that have been
found.
+ * @throws SQLException if an error occurred while querying the database.
+ */
+ final void findCodesFromAlias(final TableInfo source, final String
pattern, final String name, final Collection<Integer> addTo)
+ throws SQLException
+ {
+ final PreparedStatement stmt = prepareStatement(
+ "AliasKey",
+ "SELECT OBJECT_CODE, ALIAS"
+ + " FROM \"Alias\""
+ + " WHERE OBJECT_TABLE_NAME=? AND ALIAS LIKE ?");
+ stmt.setString(1, translator.toActualTableName(source.table));
+ stmt.setString(2, pattern);
+ try (ResultSet result = stmt.executeQuery()) {
+ while (result.next()) {
+ if (SQLUtilities.filterFalsePositive(name,
result.getString(2))) {
+ addTo.add(getOptionalInteger(result, 1));
}
}
}
@@ -829,29 +844,27 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* in their {@code finally} block.</li>
* </ul>
*
- * @param table the table where the code should appears.
- * @param sql the SQL statement to use for creating the {@link
PreparedStatement} object.
- * Will be used only if no prepared statement was already
created for the given code.
- * @param codes the codes of the object to create, as an array of length
1 or 2.
+ * @param source information about the table where the code should
appear.
+ * @param sql the <abbr>SQL</abbr> statement to use for creating the
{@link PreparedStatement} object.
+ * Will be used only if no prepared statement was already
created for the given code.
+ * @param codes the codes of the object to create, as an array of
length 1 or 2.
* @return the result of the query.
* @throws SQLException if an error occurred while querying the database.
*/
- private ResultSet executeSingletonQuery(final String table, final String
sql, final String... codes)
+ private ResultSet executeSingletonQuery(final TableInfo source, final
String sql, final String... codes)
throws SQLException, FactoryException
{
assert Thread.holdsLock(this);
- assert sql.contains('"' + table + '"') : table;
- // assert (codeColumn == null) || sql.contains(codeColumn) ||
table.equals("Extent") : codeColumn;
- // assert (nameColumn == null) || sql.contains(nameColumn) ||
table.equals("Extent") : nameColumn;
- final int[] keys = toPrimaryKeys(table, codes);
- currentSingletonQuery = new QueryID(table, keys,
currentSingletonQuery);
+ assert source.validate(sql) : source;
+ final int[] keys = toPrimaryKeys(source, codes);
+ currentSingletonQuery = new QueryID(source.table, keys,
currentSingletonQuery);
if (currentSingletonQuery.isAlreadyInProgress()) {
throw new FactoryDataException(resources().getString(
Resources.Keys.RecursiveCreateCallForCode_2,
- TableInfo.getObjectClassName(table).orElse(table),
+ source.type.getSimpleName(),
(codes.length == 1) ? codes[0] : Arrays.toString(codes)));
}
- return executeQueryForCodes(table, sql, keys);
+ return executeQueryForCodes(source.table, sql, keys);
}
/**
@@ -1184,11 +1197,11 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
/**
* Logs a warning saying that the given code is deprecated and returns the
code of the proposed replacement.
*
- * @param table the table of the deprecated code.
+ * @param source information about the table where the deprecated object
is found.
* @param code the deprecated code.
* @return the proposed replacement (may be the "(none)" text). Never
empty.
*/
- private String getReplacement(final String table, final Integer code,
final Locale locale) throws SQLException {
+ private String getReplacement(final TableInfo source, final Integer code,
final Locale locale) throws SQLException {
String reason = null;
String replacedBy;
search: try (ResultSet result = executeMetadataQuery("Deprecation",
@@ -1196,7 +1209,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
+ " FROM \"Deprecation\""
+ " WHERE OBJECT_TABLE_NAME=?"
+ " AND OBJECT_CODE=?",
- translator.toActualTableName(table), code))
+ translator.toActualTableName(source.table), code))
{
while (result.next()) {
reason = getOptionalString (result, 1);
@@ -1216,7 +1229,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
if (!quiet) {
Logging.completeAndLog(LOGGER,
EPSGDataAccess.class,
-
"create".concat(TableInfo.getObjectClassName(table).orElse("")),
+ "create".concat(source.type.getSimpleName()),
Resources.forLocale(locale).createLogRecord(
Level.WARNING,
Resources.Keys.DeprecatedCode_3,
@@ -1244,7 +1257,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* }
* }
*
- * @param table the table on which a query has been executed.
+ * @param source information about 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.
@@ -1255,7 +1268,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* @return the name together with a set of properties.
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
- private Map<String,Object> createProperties(final String table,
+ private Map<String,Object> createProperties(final TableInfo source,
final Integer code,
String name,
// May be replaced by an alias.
final CharSequence description,
@@ -1270,7 +1283,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* se we need to fetch and store the extent before to populate the
`properties` map.
*/
final Extent extent = (extentCode == null) ? null :
createExtent(extentCode);
- final String actualTable = translator.toActualTableName(table);
+ final String actualTable = translator.toActualTableName(source.table);
/*
* Get all domains for the object identified by the given code.
* The table used nere is new in version 10 of EPSG database.
@@ -1359,7 +1372,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final ImmutableIdentifier identifier;
if (deprecated) {
properties.put(AbstractIdentifiedObject.DEPRECATED_KEY,
Boolean.TRUE);
- final String replacedBy = getReplacement(table, code, locale);
+ final String replacedBy = getReplacement(source, code, locale);
identifier = new DeprecatedCode(
authority,
Constants.EPSG,
@@ -1419,21 +1432,20 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final boolean isPrimaryKey = isPrimaryKey(code);
final var query = new StringBuilder("SELECT ");
final int queryStart = query.length();
- int found = -1;
+ TableInfo found = null;
try {
final int key = isPrimaryKey ? toPrimaryKeys(null, code)[0] : 0;
- for (int i = 0; i < TableInfo.EPSG.length; i++) {
- final TableInfo table = TableInfo.EPSG[i];
- final String column = isPrimaryKey ? table.codeColumn :
table.nameColumn;
- if (column == null) {
+ for (final TableInfo source : TableInfo.values()) {
+ if (!(source.isCandidate() &&
IdentifiedObject.class.isAssignableFrom(source.type))) {
continue;
}
+ final String column = isPrimaryKey ? source.codeColumn :
source.nameColumn;
query.setLength(queryStart);
- query.append(table.codeColumn);
+ query.append(source.codeColumn);
if (!isPrimaryKey) {
query.append(", ").append(column); // Only for
filterFalsePositive(…).
}
- query.append(" FROM ").append(table.fromClause)
+ query.append(" FROM ").append(source.fromClause)
.append(" WHERE ").append(column).append(isPrimaryKey ? "
= ?" : " LIKE ?");
try (PreparedStatement stmt =
connection.prepareStatement(translator.apply(query.toString()))) {
/*
@@ -1443,7 +1455,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
if (isPrimaryKey) {
stmt.setInt(1, key);
} else {
- stmt.setString(1, SQLUtilities.toLikePattern(code,
false, translator.wildcardEscape));
+ stmt.setString(1, toLikePattern(code));
}
Integer present = null;
try (ResultSet result = stmt.executeQuery()) {
@@ -1454,10 +1466,10 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
}
}
if (present != null) {
- if (found >= 0) {
+ if (found != null) {
throw new
FactoryDataException(error().getString(Errors.Keys.DuplicatedIdentifier_1,
code));
}
- found = i;
+ found = source;
}
}
}
@@ -1467,18 +1479,18 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
/*
* If a record has been found in one table, then delegates to the
appropriate method.
*/
- if (found >= 0) {
+ if (found != null) {
switch (found) {
- case 0: return createCoordinateReferenceSystem(code);
- case 1: return createCoordinateSystem (code);
- case 2: return createCoordinateSystemAxis (code);
- case 3: return createDatum (code);
- case 4: return createEllipsoid (code);
- case 5: return createPrimeMeridian (code);
- case 6: return createCoordinateOperation (code);
- case 7: return createOperationMethod (code);
- case 8: return createParameterDescriptor (code);
- case 9: break; // Cannot cast Unit to IdentifiedObject
+ case CRS: return
createCoordinateReferenceSystem(code);
+ case CS: return createCoordinateSystem
(code);
+ case AXIS: return createCoordinateSystemAxis
(code);
+ case DATUM: return createDatum
(code);
+ case ELLIPSOID: return createEllipsoid
(code);
+ case PRIME_MERIDIAN: return createPrimeMeridian
(code);
+ case OPERATION: return createCoordinateOperation
(code);
+ case METHOD: return createOperationMethod
(code);
+ case PARAMETER: return createParameterDescriptor
(code);
+ case UNIT: break; // Cannot cast Unit to
IdentifiedObject
default: throw new AssertionError(found); //
Should not happen
}
}
@@ -1562,7 +1574,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
CoordinateReferenceSystem returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Coordinate Reference System",
+ TableInfo.CRS,
"SELECT"+ /* column 1 */ " COORD_REF_SYS_CODE,"
+ /* column 2 */ " COORD_REF_SYS_NAME,"
+ /* column 3 */ " AREA_OF_USE_CODE," //
Deprecated since EPSG version 10 (always NULL)
@@ -1806,7 +1818,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Coordinate Reference System", epsg, name, null, area,
scope, remarks, deprecated);
+ TableInfo.CRS, epsg, name, null, area, scope, remarks,
deprecated);
final CoordinateReferenceSystem crs = create(constructor,
owner.crsFactory, properties);
returnValue = ensureSingleton(crs, returnValue, code);
if (result.isClosed()) break; // See createProperties(…) for
explanation.
@@ -1875,7 +1887,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
Datum returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Datum",
+ TableInfo.DATUM,
"SELECT"+ /* column 1 */ " DATUM_CODE,"
+ /* column 2 */ " DATUM_NAME,"
+ /* column 3 */ " DATUM_TYPE,"
@@ -1968,7 +1980,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final IdentifiedObject conventionalRS =
createConventionalRS(convRSCode);
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Datum", epsg, name, null, area, scope, remarks,
deprecated);
+ TableInfo.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);
@@ -2102,7 +2114,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
var returnValue = (IdentifiedObject) localCache.get(cacheKey);
if (returnValue == null) {
try (ResultSet result = executeQueryForCodes(
- "Conventional RS",
+ TableInfo.CONVENTIONAL_RS.table,
"SELECT"+ /* column 1 */ " CONVENTIONAL_RS_CODE,"
+ /* column 2 */ " CONVENTIONAL_RS_NAME,"
+ /* column 3 */ " REMARKS,"
@@ -2121,7 +2133,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Conventional RS", epsg, name, null, null, null,
remarks, deprecated);
+ TableInfo.CONVENTIONAL_RS, epsg, name, null, null,
null, remarks, deprecated);
returnValue = ensureSingleton(new
AbstractIdentifiedObject(properties), returnValue, code);
if (result.isClosed()) break; // See createProperties(…)
for explanation.
}
@@ -2163,7 +2175,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
Ellipsoid returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Ellipsoid",
+ TableInfo.ELLIPSOID,
"SELECT"+ /* column 1 */ " ELLIPSOID_CODE,"
+ /* column 2 */ " ELLIPSOID_NAME,"
+ /* column 3 */ " SEMI_MAJOR_AXIS,"
@@ -2202,7 +2214,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Ellipsoid", epsg, name, null, null, null, remarks,
deprecated);
+ TableInfo.ELLIPSOID, epsg, name, null, null, null,
remarks, deprecated);
final Ellipsoid ellipsoid;
if (useSemiMinor) {
// We only have semiMinorAxis defined. It is OK
@@ -2265,7 +2277,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
PrimeMeridian returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Prime Meridian",
+ TableInfo.PRIME_MERIDIAN,
"SELECT"+ /* column 1 */ " PRIME_MERIDIAN_CODE,"
+ /* column 2 */ " PRIME_MERIDIAN_NAME,"
+ /* column 3 */ " GREENWICH_LONGITUDE,"
@@ -2288,7 +2300,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* 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),
+ createProperties(TableInfo.PRIME_MERIDIAN, epsg, name,
null, null, null, remarks, deprecated),
longitude, unit);
returnValue = ensureSingleton(primeMeridian, returnValue,
code);
if (result.isClosed()) break; // See createProperties(…) for
explanation.
@@ -2344,7 +2356,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
final var deferred = new ArrayList<Map.Entry<DefaultVerticalExtent,
Integer>>();
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Extent",
+ TableInfo.EXTENT,
"SELECT"+ /* column 1 */ " EXTENT_DESCRIPTION,"
+ /* column 2 */ " BBOX_SOUTH_BOUND_LAT,"
+ /* column 3 */ " BBOX_NORTH_BOUND_LAT,"
@@ -2461,7 +2473,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
CoordinateSystem returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Coordinate System",
+ TableInfo.CS,
"SELECT"+ /* column 1 */ " COORD_SYS_CODE,"
+ /* column 2 */ " COORD_SYS_NAME,"
+ /* column 3 */ " COORD_SYS_TYPE,"
@@ -2496,7 +2508,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Coordinate System", epsg, name, null, null, null,
remarks, deprecated);
+ TableInfo.CS, epsg, name, null, null, null, remarks,
deprecated);
/*
* The following switch statement should have a case for all
"CS Kind" values enumerated
* in the `Prepare.sql` file, except that the values in this
Java code are in lower cases.
@@ -2622,7 +2634,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
CoordinateSystemAxis returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Coordinate Axis",
+ TableInfo.AXIS,
"SELECT"+ /* column 1 */ " COORD_AXIS_CODE,"
+ /* column 2 */ " COORD_AXIS_NAME_CODE,"
+ /* column 3 */ " COORD_AXIS_ORIENTATION,"
@@ -2649,7 +2661,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
* 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),
+ createProperties(TableInfo.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.
@@ -2762,7 +2774,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
Unit<?> returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Unit of Measure",
+ TableInfo.UNIT,
"SELECT"+ /* column 1 */ " UOM_CODE,"
+ /* column 2 */ " FACTOR_B,"
+ /* column 3 */ " FACTOR_C,"
@@ -2844,7 +2856,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
ParameterDescriptor<?> returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Coordinate_Operation Parameter",
+ TableInfo.PARAMETER,
"SELECT"+ /* column 1 */ " PARAMETER_CODE,"
+ /* column 2 */ " PARAMETER_NAME,"
+ /* column 3 */ " DESCRIPTION,"
@@ -2872,7 +2884,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
*/
type = Double.class;
try (ResultSet r = executeQueryForCodes(
- "ParameterType",
+ "Parameter Type",
"SELECT PARAM_VALUE_FILE_REF"
+ " FROM \"Coordinate_Operation Parameter
Value\""
+ " WHERE PARAM_VALUE_FILE_REF IS NOT NULL"
@@ -2896,7 +2908,7 @@ search: try (ResultSet result =
executeMetadataQuery("Deprecation",
*/
units = new LinkedHashSet<>();
try (ResultSet r = executeQueryForCodes(
- "ParameterUnit",
+ "Parameter Unit",
"SELECT UOM_CODE"
+ " FROM \"Coordinate_Operation Parameter
Value\""
+ " WHERE (PARAMETER_CODE = ?)"
@@ -2929,7 +2941,7 @@ next: while (r.next()) {
*/
InternationalString isReversible = null;
try (ResultSet r = executeQueryForCodes(
- "ParameterSign",
+ "Parameter Sign",
"SELECT DISTINCT PARAM_SIGN_REVERSAL"
+ " FROM \"Coordinate_Operation Parameter
Usage\""
+ " WHERE (PARAMETER_CODE = ?)", epsg))
@@ -2964,7 +2976,7 @@ next: while (r.next()) {
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Coordinate_Operation Parameter", epsg, name, null,
null, null, isReversible, deprecated);
+ TableInfo.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);
@@ -3094,7 +3106,7 @@ next: while (r.next()) {
OperationMethod returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Coordinate_Operation Method",
+ TableInfo.METHOD,
"SELECT"+ /* column 1 */ " COORD_OP_METHOD_CODE,"
+ /* column 2 */ " COORD_OP_METHOD_NAME,"
+ /* column 3 */ " REMARKS,"
@@ -3121,7 +3133,7 @@ next: while (r.next()) {
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Coordinate_Operation Method", epsg, name, null, null,
null, remarks, deprecated);
+ TableInfo.METHOD, epsg, name, null, null, null,
remarks, deprecated);
/*
* Note: we do not store the formula at this time, because the
text is very verbose and rarely used.
*/
@@ -3170,7 +3182,7 @@ next: while (r.next()) {
CoordinateOperation returnValue = null;
final QueryID previousSingletonQuery = currentSingletonQuery;
try (ResultSet result = executeSingletonQuery(
- "Coordinate_Operation",
+ TableInfo.OPERATION,
"SELECT"+ /* column 1 */ " COORD_OP_CODE,"
+ /* column 2 */ " COORD_OP_NAME,"
+ /* column 3 */ " COORD_OP_TYPE,"
@@ -3316,7 +3328,7 @@ next: while (r.next()) {
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "Coordinate_Operation", epsg, name, null, area, scope,
remarks, deprecated);
+ TableInfo.OPERATION, epsg, name, null, area, scope,
remarks, deprecated);
properties.put(CoordinateOperations.OPERATION_TYPE_KEY,
operationType);
properties.put(CoordinateOperations.PARAMETERS_KEY,
parameterValues);
properties.put(CoordinateOperation .OPERATION_VERSION_KEY,
version);
@@ -3399,7 +3411,7 @@ next: while (r.next()) {
* and supersession tables.
*/
final List<String> codes = Arrays.asList(set.getAuthorityCodes());
- sort("Coordinate_Operation", codes,
Integer::parseInt).ifPresent((sorted) -> {
+ sort(TableInfo.OPERATION, codes,
Integer::parseInt).ifPresent((sorted) -> {
set.setAuthorityCodes(sorted.mapToObj(Integer::toString).toArray(String[]::new));
});
} catch (SQLException exception) {
@@ -3442,19 +3454,19 @@ next: while (r.next()) {
* Except for the codes moved as a result of pairwise ordering, this
method tries to preserve the old
* ordering of the supplied codes (since deprecated operations should
already be last).
*
- * @param table the table of the objects for which to check for
supersession.
- * @param codes the codes to sort. This collection will not be modified
by this method.
- * @param parser the method to invoke for converting a {@code codes}
element to an integer.
+ * @param source the table of the objects for which to check for
supersession.
+ * @param codes the codes to sort. This collection will not be modified
by this method.
+ * @param parser the method to invoke for converting a {@code codes}
element to an integer.
* @return codes of sorted elements, or empty if this method did not
change the codes order.
*/
- final synchronized <C extends Comparable<?>> Optional<IntStream>
sort(final String table, final Collection<C> codes, final ToIntFunction<C>
parser)
+ final synchronized <C extends Comparable<?>> Optional<IntStream>
sort(final TableInfo source, final Collection<C> codes, final ToIntFunction<C>
parser)
throws SQLException, FactoryException
{
final int size = codes.size();
if (size > 1) try {
final var elements = new ObjectPertinence[size];
final var extents = new ArrayList<String>();
- final String actualTable = translator.toActualTableName(table);
+ final String actualTable =
translator.toActualTableName(source.table);
int count = 0;
for (final C code : codes) {
final int key;
@@ -3473,20 +3485,15 @@ next: while (r.next()) {
* Note: if this block is deleted, consider deleting also
* the finally block and the `TableInfo.areaOfUse` flag.
*/
- for (final TableInfo info : TableInfo.EPSG) {
- if (table.equals(info.table)) {
- if (info.areaOfUse) {
- try (ResultSet result = executeQueryForCodes(
- "Area", // Table from EPSG version
9. Does not exist anymore in version 10.
- "SELECT AREA_OF_USE_CODE FROM \"" +
table + "\" WHERE " + info.codeColumn + "=?",
- key))
- {
- while (result.next()) {
- extents.add(getString(code, result,
1));
- }
- }
+ if (source.areaOfUse) {
+ try (ResultSet result = executeQueryForCodes(
+ "Area", // Table from EPSG version 9. Does
not exist anymore in version 10.
+ "SELECT AREA_OF_USE_CODE FROM \"" +
source.table + "\" WHERE " + source.codeColumn + "=?",
+ key))
+ {
+ while (result.next()) {
+ extents.add(getString(code, result, 1));
}
- break;
}
}
}
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 5acf36cebb..aed9fa2b7c 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
@@ -16,8 +16,8 @@
*/
package org.apache.sis.referencing.factory.sql;
-import java.util.Optional;
import javax.measure.Unit;
+import org.opengis.metadata.extent.Extent;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
@@ -25,143 +25,172 @@ import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.opengis.parameter.ParameterDescriptor;
import org.apache.sis.referencing.privy.WKTKeywords;
-import org.apache.sis.util.CharSequences;
// Specific to the geoapi-4.0 branch:
import org.apache.sis.referencing.crs.DefaultGeocentricCRS;
/**
- * Information about a specific table. This class uses the mixed-case variant
of the <abbr>EPSG</abbr> table names.
- * If needed, those names will be converted by {@link
SQLTranslator#apply(String)} to the actual table names.
+ * Information (such as columns of particular interest) about a specific
<abbr>EPSG</abbr> table.
+ * This class uses the mixed-case variant of the <abbr>EPSG</abbr> {@linkplain
#table table names}.
+ * The {@link #values()} can be tested in preference order for finding the
table of an object.
+ * Those tables are used by the {@link EPSGDataAccess#createObject(String)}
method in order to
+ * detect which of the following methods should be invoked for a given code:
+ *
+ * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)}
+ * {@link EPSGDataAccess#createCoordinateSystem(String)}
+ * {@link EPSGDataAccess#createDatum(String)}
+ * {@link EPSGDataAccess#createEllipsoid(String)}
+ * {@link EPSGDataAccess#createUnit(String)}
+ *
+ * <h4>Ambiguity</h4>
+ * As of ISO 19111:2019, we have no standard way to identify the geocentric
case from a {@link Class} argument
+ * because the standard does not provide the {@code GeocentricCRS} interface.
This implementation fallbacks on
+ * the <abbr>SIS</abbr>-specific geocentric <abbr>CRS</abbr> class. This
special case is implemented in the
+ * {@link #where(EPSGDataAccess, IdentifiedObject, StringBuilder)} method.
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
-final class TableInfo {
+enum TableInfo {
/**
- * The item used for coordinate reference systems.
+ * Information about the "Coordinate Reference System" table.
*/
- static final TableInfo CRS;
+ CRS(CoordinateReferenceSystem.class,
+ "\"Coordinate Reference System\"",
+ "COORD_REF_SYS_CODE",
+ "COORD_REF_SYS_NAME",
+ "COORD_REF_SYS_KIND",
+ new Class<?>[] { ProjectedCRS.class, GeographicCRS.class,
DefaultGeocentricCRS.class,
+ VerticalCRS.class, CompoundCRS.class,
EngineeringCRS.class,
+ DerivedCRS.class, TemporalCRS.class,
ParametricCRS.class}, // See comment below
+ new String[] {"projected", "geographic",
"geocentric",
+ "vertical", "compound",
"engineering",
+ "derived", "temporal",
"parametric"}, // See comment below
+ "SHOW_CRS", true),
+ /*
+ * Above declaration could omit Derived, Temporal and Parametric
cases because they are not defined
+ * by the EPSG repository (at least as of version 8.9). In
particular we are not sure if EPSG would
+ * chose to use "time" or "temporal". However, omitting those
types slow down a lot the search for
+ * CRS matching an existing one (even if it still work).
+ */
/**
- * The item used for datums.
+ * Information about the "Datum" table.
*/
- static final TableInfo DATUM;
+ DATUM(Datum.class,
+ "\"Datum\"",
+ "DATUM_CODE",
+ "DATUM_NAME",
+ "DATUM_TYPE",
+ new Class<?>[] { GeodeticDatum.class, VerticalDatum.class,
EngineeringDatum.class,
+ TemporalDatum.class, ParametricDatum.class},
+ new String[] {"geodetic", "vertical",
"engineering",
+ "temporal", "parametric"}, //
Same comment as in the CRS case above.
+ null, true),
/**
- * The item used for ellipsoids.
+ * Information about the "Conventional RS" table.
+ * This enumeration usually needs to be ignored because the current type
is too generic.
+ *
+ * @see #isCandidate()
*/
- static final TableInfo ELLIPSOID;
+ CONVENTIONAL_RS(IdentifiedObject.class,
+ "\"Conventional RS\"",
+ "CONVENTIONAL_RS_CODE",
+ "CONVENTIONAL_RS_NAME",
+ null, null, null, null, false),
/**
- * List of tables and columns to test for existence of codes values.
- * Those tables are used by the {@link
EPSGDataAccess#createObject(String)} method
- * in order to detect which of the following methods should be invoked for
a given code:
- *
- * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * {@link EPSGDataAccess#createCoordinateSystem(String)}
- * {@link EPSGDataAccess#createDatum(String)}
- * {@link EPSGDataAccess#createEllipsoid(String)}
- * {@link EPSGDataAccess#createUnit(String)}
- *
- * The order is significant: it is the key for a {@code switch} statement.
- *
- * <h4>Ambiguity</h4>
- * As of ISO 19111:2019, we have no standard way to identify the
geocentric case from a {@link Class} argument
- * because the standard does not provide the {@code GeocentricCRS}
interface. This implementation fallbacks on
- * the SIS-specific geocentric CRS class, with a {@link
#where(EPSGDataAccess, IdentifiedObject, StringBuilder)}
- * method which will substitute implementation-neutral objects by the
Apache SIS class.
+ * Information about the "Ellipsoid" table.
*/
- static final TableInfo[] EPSG = {
- CRS = new TableInfo(CoordinateReferenceSystem.class,
- "\"Coordinate Reference System\"",
- "COORD_REF_SYS_CODE",
- "COORD_REF_SYS_NAME",
- "COORD_REF_SYS_KIND",
- new Class<?>[] { ProjectedCRS.class, GeographicCRS.class,
DefaultGeocentricCRS.class,
- VerticalCRS.class, CompoundCRS.class,
EngineeringCRS.class,
- DerivedCRS.class, TemporalCRS.class,
ParametricCRS.class}, // See comment below
- new String[] {"projected", "geographic",
"geocentric",
- "vertical", "compound",
"engineering",
- "derived", "temporal",
"parametric"}, // See comment below
- "SHOW_CRS", true),
- /*
- * Above declaration could omit Derived, Temporal and
Parametric cases because they are not defined
- * by the EPSG repository (at least as of version 8.9). In
particular we are not sure if EPSG would
- * chose to use "time" or "temporal". However, omitting those
types slow down a lot the search for
- * CRS matching an existing one (even if it still work).
- */
-
- new TableInfo(CoordinateSystem.class,
- "\"Coordinate System\"",
- "COORD_SYS_CODE",
- "COORD_SYS_NAME",
- "COORD_SYS_TYPE",
- new Class<?>[] {CartesianCS.class, EllipsoidalCS.class,
VerticalCS.class, LinearCS.class,
- SphericalCS.class, PolarCS.class,
CylindricalCS.class,
- TimeCS.class, ParametricCS.class,
AffineCS.class},
- new String[] {WKTKeywords.Cartesian,
WKTKeywords.ellipsoidal, WKTKeywords.vertical, WKTKeywords.linear,
- WKTKeywords.spherical, WKTKeywords.polar,
WKTKeywords.cylindrical,
- WKTKeywords.temporal,
WKTKeywords.parametric, WKTKeywords.affine}, // Same comment as in the
CRS case above.
- null, false),
+ ELLIPSOID(Ellipsoid.class,
+ "\"Ellipsoid\"",
+ "ELLIPSOID_CODE",
+ "ELLIPSOID_NAME",
+ null, null, null, null, false),
- new TableInfo(CoordinateSystemAxis.class,
- "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis Name\"
AS CAN " +
- "ON
CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
- "COORD_AXIS_CODE",
- "COORD_AXIS_NAME",
- null, null, null, null, false),
+ /**
+ * Information about the "Prime Meridian" table.
+ */
+ PRIME_MERIDIAN(PrimeMeridian.class,
+ "\"Prime Meridian\"",
+ "PRIME_MERIDIAN_CODE",
+ "PRIME_MERIDIAN_NAME",
+ null, null, null, null, false),
- DATUM = new TableInfo(Datum.class,
- "\"Datum\"",
- "DATUM_CODE",
- "DATUM_NAME",
- "DATUM_TYPE",
- new Class<?>[] { GeodeticDatum.class, VerticalDatum.class,
EngineeringDatum.class,
- TemporalDatum.class, ParametricDatum.class},
- new String[] {"geodetic", "vertical",
"engineering",
- "temporal", "parametric"},
// Same comment as in the CRS case above.
- null, true),
+ /**
+ * Information about the "Coordinate_Operation" table.
+ */
+ OPERATION(CoordinateOperation.class,
+ "\"Coordinate_Operation\"",
+ "COORD_OP_CODE",
+ "COORD_OP_NAME",
+ "COORD_OP_TYPE",
+ new Class<?>[] { Conversion.class, Transformation.class},
+ new String[] {"conversion", "transformation"},
+ "SHOW_OPERATION", true),
- ELLIPSOID = new TableInfo(Ellipsoid.class,
- "\"Ellipsoid\"",
- "ELLIPSOID_CODE",
- "ELLIPSOID_NAME",
- null, null, null, null, false),
+ /**
+ * Information about the "Coordinate_Operation Method" table.
+ */
+ METHOD(OperationMethod.class,
+ "\"Coordinate_Operation Method\"",
+ "COORD_OP_METHOD_CODE",
+ "COORD_OP_METHOD_NAME",
+ null, null, null, null, false),
- new TableInfo(PrimeMeridian.class,
- "\"Prime Meridian\"",
- "PRIME_MERIDIAN_CODE",
- "PRIME_MERIDIAN_NAME",
- null, null, null, null, false),
+ /**
+ * Information about the "Coordinate_Operation Parameter" table.
+ */
+ PARAMETER(ParameterDescriptor.class,
+ "\"Coordinate_Operation Parameter\"",
+ "PARAMETER_CODE",
+ "PARAMETER_NAME",
+ null, null, null, null, false),
- new TableInfo(CoordinateOperation.class,
- "\"Coordinate_Operation\"",
- "COORD_OP_CODE",
- "COORD_OP_NAME",
- "COORD_OP_TYPE",
- new Class<?>[] { Conversion.class, Transformation.class},
- new String[] {"conversion", "transformation"},
- "SHOW_OPERATION", true),
+ /**
+ * Information about the "Extent" table.
+ */
+ EXTENT(Extent.class,
+ "\"Extent\"",
+ "EXTENT_CODE",
+ "EXTENT_NAME",
+ null, null, null, null, false),
- new TableInfo(OperationMethod.class,
- "\"Coordinate_Operation Method\"",
- "COORD_OP_METHOD_CODE",
- "COORD_OP_METHOD_NAME",
- null, null, null, null, false),
+ /**
+ * Information about the "Coordinate System" table.
+ */
+ CS(CoordinateSystem.class,
+ "\"Coordinate System\"",
+ "COORD_SYS_CODE",
+ "COORD_SYS_NAME",
+ "COORD_SYS_TYPE",
+ new Class<?>[] {CartesianCS.class, EllipsoidalCS.class,
VerticalCS.class, LinearCS.class,
+ SphericalCS.class, PolarCS.class,
CylindricalCS.class,
+ TimeCS.class, ParametricCS.class,
AffineCS.class},
+ new String[] {WKTKeywords.Cartesian, WKTKeywords.ellipsoidal,
WKTKeywords.vertical, WKTKeywords.linear,
+ WKTKeywords.spherical, WKTKeywords.polar,
WKTKeywords.cylindrical,
+ WKTKeywords.temporal, WKTKeywords.parametric,
WKTKeywords.affine}, // Same comment as in the CRS case above.
+ null, false),
- new TableInfo(ParameterDescriptor.class,
- "\"Coordinate_Operation Parameter\"",
- "PARAMETER_CODE",
- "PARAMETER_NAME",
- null, null, null, null, false),
+ /**
+ * Information about the "Coordinate Axis" table.
+ */
+ AXIS(CoordinateSystemAxis.class,
+ "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis Name\" AS
CAN " +
+ "ON
CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
+ "COORD_AXIS_CODE",
+ "COORD_AXIS_NAME",
+ null, null, null, null, false),
- new TableInfo(Unit.class,
- "\"Unit of Measure\"",
- "UOM_CODE",
- "UNIT_OF_MEAS_NAME",
- null, null, null, null, false),
- };
+ /**
+ * Information about the "Unit of Measure" table.
+ */
+ UNIT(Unit.class,
+ "\"Unit of Measure\"",
+ "UOM_CODE",
+ "UNIT_OF_MEAS_NAME",
+ null, null, null, null, false);
/**
* The class of object to be created (usually a GeoAPI interface).
@@ -170,6 +199,7 @@ final class TableInfo {
/**
* The table name in mixed-case and without quotes.
+ * This name can be converted to the actual table names by {@link
SQLTranslator#toActualTableName(String)}.
*/
final String table;
@@ -186,7 +216,7 @@ final class TableInfo {
final String codeColumn;
/**
- * Column name for the name (usually with the {@code "_NAME"} suffix), or
{@code null}.
+ * Column name for the name (usually with the {@code "_NAME"} suffix).
*/
final String nameColumn;
@@ -222,7 +252,7 @@ final class TableInfo {
* @param type the class of object to be created (usually a GeoAPI
interface).
* @param fromClause The <abbr>SQL</abbr> fragment to use in the {@code
FROM} clause, including quotes.
* @param codeColumn column name for the code (usually with the {@code
"_CODE"} suffix).
- * @param nameColumn column name for the name (usually with the {@code
"_NAME"} suffix), or {@code null}.
+ * @param nameColumn column name for the name (usually with the {@code
"_NAME"} suffix).
* @param typeColumn column type for the type (usually with the {@code
"_TYPE"} suffix), or {@code null}.
* @param subTypes sub-interfaces of {@link #type} to handle, or {@code
null} if none.
* @param typeNames names of {@code subTypes} in the database, or {@code
null} if none.
@@ -243,30 +273,16 @@ final class TableInfo {
this.typeNames = typeNames;
this.showColumn = showColumn;
this.areaOfUse = areaOfUse;
- table = fromClause.substring(fromClause.indexOf('"') + 1,
fromClause.lastIndexOf('"')).intern();
+ final int start = fromClause.indexOf('"') + 1;
+ table = fromClause.substring(start, fromClause.indexOf('"',
start)).intern();
}
/**
- * Returns the class of objects created from the given table. The given
table name should be one
- * of the values enumerated in the {@code "Table Name"} types of the
{@code Prepare.sql} file.
- * The name may be prefixed by {@code "epsg_"} and may contain
abbreviations of the full name.
- * For example, {@code "epsg_coordoperation"} is considered as a match for
{@code "Coordinate_Operation"}.
- *
- * @param table mixed-case name of an <abbr>EPSG</abbr> table.
- * @return name of the class of objects created from the given table.
+ * 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.
*/
- static Optional<String> getObjectClassName(String table) {
- if (table != null) {
- if (table.startsWith(SQLTranslator.TABLE_PREFIX)) {
- table = table.substring(SQLTranslator.TABLE_PREFIX.length());
- }
- for (final TableInfo info : EPSG) {
- if (CharSequences.isAcronymForWords(table, info.table)) {
- return Optional.of(info.type.getSimpleName());
- }
- }
- }
- return Optional.empty();
+ final boolean isCandidate() {
+ return type != IdentifiedObject.class;
}
/**
@@ -326,4 +342,23 @@ final class TableInfo {
}
return type;
}
+
+ /**
+ * Verifies that the given <abbr>SQL</abbr> statement contains the
expected table and columns.
+ * This method is for assertions only. It may either returns {@code false}
if the query is not
+ * valid, or throws an {@link AssertionError} itself for providing more
details.
+ *
+ * @param sql the <abbr>SQL</abbr> statement to validate.
+ * @return whether the statement is valid.
+ */
+ final boolean validate(final String sql) {
+ if (sql.contains(table)) {
+ if (type.isAssignableFrom(IdentifiedObject.class)) {
+ if (!sql.contains(codeColumn)) throw new
AssertionError(codeColumn);
+ if (!sql.contains(nameColumn)) throw new
AssertionError(nameColumn);
+ }
+ return true;
+ }
+ return false;
+ }
}
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/TableInfoTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/TableInfoTest.java
index e3b30f74d9..943641f75d 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/TableInfoTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/TableInfoTest.java
@@ -35,12 +35,16 @@ public final class TableInfoTest extends TestCase {
}
/**
- * Tests {@link TableInfo#getObjectClassName(String)}.
+ * Validates the enumeration values.
*/
@Test
- public void testgGetObjectClassName() {
- assertEquals("Datum",
TableInfo.getObjectClassName("epsg_datum").orElseThrow());
- assertEquals("Ellipsoid",
TableInfo.getObjectClassName("epsg_ellipsoid").orElseThrow());
- assertEquals("CoordinateReferenceSystem",
TableInfo.getObjectClassName("epsg_coordinatereferencesystem").orElseThrow());
+ public void validate() {
+ for (TableInfo info : TableInfo.values()) {
+ assertNotNull(info.type);
+ assertNotNull(info.table);
+ assertNotNull(info.codeColumn);
+ assertNotNull(info.nameColumn);
+ assertTrue(info.fromClause.contains(info.table));
+ }
}
}
diff --git
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/AxisType.java
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/AxisType.java
index a66b1c9e5c..fca5b97b5c 100644
---
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/AxisType.java
+++
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/AxisType.java
@@ -26,7 +26,7 @@ import org.apache.sis.measure.Units;
/**
- * Type of coordinate system axis, in the order they should appears for a
"normalized" coordinate reference system.
+ * Type of coordinate system axis, in the order they should appear for a
"normalized" coordinate reference system.
* The enumeration name matches the name of the {@code "axis"} attribute in
CF-convention.
* Enumeration order is the desired order of coordinate values.
*