sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/01: Merge bug fixes from branch 'geoapi-4.0' into geoapi-3.1.
Date Mon, 03 Jun 2019 13:42:20 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-3.1
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 5539286be3e286415d32f31ae3f121426d19234e
Merge: ead7586 f9009a1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jun 3 15:32:54 2019 +0200

    Merge bug fixes from branch 'geoapi-4.0' into geoapi-3.1.

 .../metadata/EllipsoidalHeightCombiner.java        |  15 +-
 .../apache/sis/coverage/grid/GridDerivation.java   |   2 +
 .../apache/sis/coverage/grid/ImageRenderer.java    |  64 +--
 .../referencing/GeodeticObjectBuilder.java         | 123 ++++-
 .../referencing/ReferencingFactoryContainer.java   |  14 +
 .../internal/referencing/ReferencingUtilities.java |  44 --
 .../sis/internal/referencing/j2d/Bezier.java       |  21 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |   7 +-
 .../apache/sis/referencing/GeodeticCalculator.java |  46 +-
 .../sis/referencing/datum/AbstractDatum.java       |   2 +-
 .../referencing/factory/sql/EPSGCodeFinder.java    | 538 +++++++++++++++++++++
 .../referencing/factory/sql/EPSGDataAccess.java    | 285 +----------
 .../sis/referencing/factory/sql/TableInfo.java     |   4 +-
 .../operation/builder/LinearTransformBuilder.java  |  18 +-
 .../operation/builder/LocalizationGridBuilder.java |   5 +-
 .../operation/projection/ConformalProjection.java  |   2 +-
 .../sis/referencing/GeodeticCalculatorTest.java    |   4 +-
 .../main/java/org/apache/sis/util/Exceptions.java  |  25 +-
 src/main/config/logging.properties                 |  11 +-
 .../storage/earthobservation/LandsatReader.java    |  99 ++--
 .../org/apache/sis/storage/geotiff/CRSBuilder.java |   2 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  | 178 +++++--
 .../org/apache/sis/internal/netcdf/CRSBuilder.java |   3 +-
 .../org/apache/sis/internal/netcdf/Convention.java |  63 +--
 .../org/apache/sis/internal/netcdf/DataType.java   |   2 +-
 .../org/apache/sis/internal/netcdf/Decoder.java    |  82 +++-
 .../java/org/apache/sis/internal/netcdf/Grid.java  |  61 +--
 .../apache/sis/internal/netcdf/GridCacheKey.java   | 251 ++++++++++
 .../apache/sis/internal/netcdf/GridMapping.java    |  33 +-
 .../org/apache/sis/internal/netcdf/Linearizer.java |   2 +-
 .../java/org/apache/sis/internal/netcdf/Node.java  | 170 +++++--
 .../apache/sis/internal/netcdf/RasterResource.java |  28 +-
 .../org/apache/sis/internal/netcdf/Resources.java  |  10 +
 .../sis/internal/netcdf/Resources.properties       |   2 +
 .../sis/internal/netcdf/Resources_fr.properties    |   2 +
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  53 +-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |  18 +-
 .../sis/internal/netcdf/impl/VariableInfo.java     | 155 +-----
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |   2 +-
 .../sis/internal/netcdf/ucar/GroupWrapper.java     |   8 +-
 .../sis/internal/netcdf/ucar/VariableWrapper.java  |  67 ++-
 .../apache/sis/storage/netcdf/MetadataReader.java  |  26 +-
 .../org/apache/sis/internal/netcdf/GridTest.java   |  23 +-
 .../org/apache/sis/internal/netcdf/TestCase.java   |   2 +-
 .../apache/sis/internal/netcdf/VariableTest.java   |  47 +-
 .../sis/internal/netcdf/impl/VariableInfoTest.java |  13 -
 .../apache/sis/internal/storage/io/ByteWriter.java | 215 ++++++++
 .../sis/internal/storage/io/package-info.java      |   2 +-
 48 files changed, 1981 insertions(+), 868 deletions(-)

diff --cc core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index 0000000,d9bf57f..0463fcf
mode 000000,100644..100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@@ -1,0 -1,538 +1,538 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.referencing.factory.sql;
+ 
+ import java.util.Set;
+ import java.util.List;
+ import java.util.Collections;
+ import java.util.LinkedHashSet;
+ import java.sql.Statement;
+ import java.sql.ResultSet;
+ import java.sql.SQLException;
+ import org.opengis.util.FactoryException;
+ import org.opengis.util.GenericName;
+ import org.opengis.metadata.Identifier;
+ import org.opengis.referencing.IdentifiedObject;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.opengis.referencing.crs.SingleCRS;
+ import org.opengis.referencing.crs.CompoundCRS;
+ import org.opengis.referencing.crs.GeodeticCRS;
+ import org.opengis.referencing.crs.TemporalCRS;
+ import org.opengis.referencing.crs.VerticalCRS;
+ import org.opengis.referencing.crs.GeneralDerivedCRS;
+ import org.opengis.referencing.cs.CoordinateSystem;
+ import org.opengis.referencing.datum.Datum;
+ import org.opengis.referencing.datum.Ellipsoid;
+ import org.opengis.referencing.datum.GeodeticDatum;
+ import org.opengis.referencing.datum.TemporalDatum;
+ import org.opengis.referencing.datum.VerticalDatum;
+ import org.opengis.referencing.datum.VerticalDatumType;
+ import org.apache.sis.internal.metadata.ReferencingServices;
+ import org.apache.sis.internal.metadata.sql.SQLUtilities;
+ import org.apache.sis.internal.referencing.Formulas;
+ import org.apache.sis.internal.system.Loggers;
+ import org.apache.sis.internal.util.CollectionsExt;
+ import org.apache.sis.metadata.iso.citation.Citations;
+ import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+ import org.apache.sis.referencing.factory.ConcurrentAuthorityFactory;
+ import org.apache.sis.util.ArraysExt;
+ import org.apache.sis.util.CharSequences;
+ import org.apache.sis.util.collection.Containers;
+ import org.apache.sis.util.logging.Logging;
+ 
+ import static org.apache.sis.internal.metadata.NameToIdentifier.Simplifier.ESRI_DATUM_PREFIX;
+ 
+ 
+ /**
+  * An implementation of {@link IdentifiedObjectFinder} which scans over a smaller set of
authority codes.
+  * This is used for finding the EPSG code of a given Coordinate Reference System or other
geodetic object.
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @version 1.0
+  * @since   0.7
+  * @module
+  */
+ final class EPSGCodeFinder extends IdentifiedObjectFinder {
+     /**
+      * The data access object to use for searching EPSG codes.
+      */
+     private final EPSGDataAccess dao;
+ 
+     /**
+      * The type of object to search, or {@code null} for using {@code object.getClass()}.
+      * This is set to a non-null value when searching for dependencies, in order to avoid
+      * confusion in an implementation class implements more than one GeoAPI interfaces.
+      *
+      * @see #isInstance(Class, IdentifiedObject)
+      */
+     private Class<? extends IdentifiedObject> declaredType;
+ 
+     /**
+      * Creates a new finder for the given data access object.
+      */
+     EPSGCodeFinder(final EPSGDataAccess dao) {
+         super(dao.owner);
+         this.dao = dao;
+     }
+ 
+     /**
+      * Lookups objects which are approximately equal to the specified object.
+      * This method temporarily disables warnings about deprecated objects.
+      */
+     @Override
+     public Set<IdentifiedObject> find(final IdentifiedObject object) throws FactoryException
{
+         final boolean old = dao.quiet;
+         dao.quiet = true;
+         try {
+             return super.find(object);
+         } finally {
+             dao.quiet = old;
+         }
+     }
+ 
+     /**
+      * Returns a description of the condition to put in a {@code WHERE} clause for an object
having
+      * the given dependency.
+      *
+      * <div class="section"><b>Implementation note:</b>
+      * The {@code super.find(…)} method performs a check (not documented in public API)
for detecting
+      * when it is invoked recursively, which is the case here. Consequently the {@code super.find(…)}
+      * behavior below is slightly different than usual: since invoked recursively, {@code
super.find(…)}
+      * checks the cache of the {@link ConcurrentAuthorityFactory} wrapper. If found, the
dependency will
+      * also be stored in the cache. This is desirable because this method may be invoked
(indirectly) in
+      * a loop for many CRS objects sharing the same {@link CoordinateSystem} or {@link Datum}
dependencies.
+      * </div>
+      *
+      * @param  column      column in the SQL query containing EPSG codes of dependency.
+      * @param  type        GeoAPI interface implemented by the dependency to search.
+      * @param  dependency  the dependency for which to search EPSG codes, or {@code null}.
+      * @param  ignoreAxes  whether to force ignoring axes. Useful for base CRS of projected
CRS.
+      * @return EPSG codes of given dependency, or {@code null} if no dependency were found.
+      */
+     private <T extends IdentifiedObject> Condition dependencies(final String column,
+             final Class<T> type, final T dependency, final boolean ignoreAxes) throws
FactoryException
+     {
+         if (dependency != null) {
+             final Class<? extends IdentifiedObject> pt = declaredType;
+             final boolean previous = isIgnoringAxes();
+             final Set<IdentifiedObject> find;
+             try {
+                 setIgnoringAxes(ignoreAxes | previous);
+                 declaredType = type;
+                 find = find(dependency);
+             } finally {
+                 declaredType = pt;
+                 setIgnoringAxes(previous);
+             }
+             final Set<Number> filters = new LinkedHashSet<>(Containers.hashMapCapacity(find.size()));
+             for (final IdentifiedObject dep : find) {
+                 Identifier id = IdentifiedObjects.getIdentifier(dep, Citations.EPSG);
+                 if (id != null) try {                                                  
// Should never be null, but let be safe.
+                     filters.add(Integer.parseInt(id.getCode()));
+                 } catch (NumberFormatException e) {
+                     Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY),
EPSGCodeFinder.class, "getCodeCandidates", e);
+                 }
+             }
+             if (!filters.isEmpty()) {
+                 return new Condition(column, filters);
+             }
+         }
+         return null;
+     }
+ 
+     /**
+      * A condition to put in a SQL {@code WHERE} clause. SQL query will be one of the forms
shown below,
+      * where {@code <column>} and {@code <values>} are {@link #column} and {@link
#values} respectively.
+      *
+      * {@preformat sql
+      *     SELECT <codeColumn> FROM <table> WHERE <column> IN (<values>)
+      *     SELECT <codeColumn> FROM <table> WHERE <column> >= <value
- ε> AND <column> <= <value + ε>
+      * }
+      *
+      * The later form is used if {@code <filters>} is a floating point value.
+      * Otherwise, {@code <filters>} are typically EPSG codes of dependencies.
+      */
+     private static class Condition {
+         /** A sentinel value for filtering by name. */
+         static final Condition NAME = new Condition("NAME", Collections.emptySet());
+ 
+         /** The column on which the condition apply. */
+         final String column;
+ 
+         /** The values of the conditions. */
+         final Set<Number> values;
+ 
+         /** Creates a new condition. */
+         Condition(final String column, final Set<Number> values) {
+             this.column = column;
+             this.values = values;
+         }
+ 
+         /**
+          * Appends this condition into the given buffer. If {@code isNext} is {@code true},
then an {@code "AND"}
+          * keyword is appended before the condition. Otherwise this method presumes that
the given buffer already
+          * ends with {@code "WHERE "} or {@code "AND "} keyword. This method does <strong>not</strong>
append a
+          * new {@code "AND"} keyword after the condition.
+          *
+          * @param  isNext  whether to append a {@code "AND"} keyword before the condition.
+          * @param  buffer  where to append the SQL fragment.
+          * @return whether a SQL fragment has been appended.
+          */
+         boolean appendToWhere(final StringBuilder buffer, final boolean isNext) {
+             if (values.isEmpty()) return false;
+             if (isNext) buffer.append(" AND ");
+             buffer.append(column);
+             if (values.size() == 1) {
+                 buffer.append('=').append(CollectionsExt.first(values));
+             } else {
+                 buffer.append(" IN (");
+                 for (final Number code : values) {
+                     buffer.append(code).append(',');
+                 }
+                 buffer.setCharAt(buffer.length() - 1, ')');
+             }
+             return true;
+         }
+ 
+         /**
+          * Appends an ordering criterion. The buffer shall ends with {@code "ORDER BY"}
keyword before
+          * this method is invoked. If this method appends a criterion, then a comma will
be appended
+          * after that criterion for allowing chaining.
+          *
+          * @param  buffer  where to append the SQL fragment.
+          */
+         void appendToOrderBy(final StringBuilder buffer) {
+         }
+ 
+         /**
+          * Returns a string representation of this condition for debugging purpose.
+          */
+         @Override
+         public final String toString() {
+             final StringBuilder buffer = new StringBuilder(50);
+             appendToWhere(buffer, false);
+             return buffer.toString();
+         }
+     }
+ 
+     /**
+      * A condition for a floating point value with a tolerance.
+      */
+     private static final class FloatCondition extends Condition {
+         /** Creates a new condition for the given value. */
+         FloatCondition(final String column, final double value) {
+             super(column, Collections.singleton(value));
+         }
+ 
+         /**
+          * Appends a condition with a numeric value assumed a linear distance in metres.
+          * The tolerance threshold is 1 centimetre for a planet of the size of Earth.
+          */
+         @Override
+         boolean appendToWhere(final StringBuilder buffer, final boolean isNext) {
+             if (isNext) buffer.append(" AND ");
+             final double value = values.iterator().next().doubleValue();
+             final double tolerance = Math.abs((Formulas.LINEAR_TOLERANCE / ReferencingServices.AUTHALIC_RADIUS)
* value);
+             buffer.append(column).append(">=").append(value - tolerance).append(" AND
")
+                   .append(column).append("<=").append(value + tolerance);
+             return true;
+         }
+ 
+         /**
+          * Appends an ordering condition.
+          */
+         @Override
+         void appendToOrderBy(final StringBuilder buffer) {
+             final double value = values.iterator().next().doubleValue();
+             buffer.append("ABS(").append(column).append('-').append(value).append("), ");
+         }
+     }
+ 
+     /**
+      * Returns {@code true} if the given object implements the given interface, ignoring
interfaces
+      * that are not subtypes of {@link #declaredType}. This method is used for avoiding
confusion if
+      * the given object implements more than one GeoAPI interfaces. Test order matter: CRS
should be
+      * tested first in order to have precedence over datum types.
+      */
+     private boolean isInstance(final Class<? extends IdentifiedObject> type, final
IdentifiedObject object) {
+         return (declaredType == null || type.isAssignableFrom(declaredType)) &&
type.isInstance(object);
+     }
+ 
+     /**
+      * Returns a set of authority codes that <strong>may</strong> identify the
same object than the specified one.
+      * This implementation tries to get a smaller set than what {@link EPSGDataAccess#getAuthorityCodes(Class)}
+      * would produce. Deprecated objects must be last in iteration order.
+      */
+     @Override
+     protected Set<String> getCodeCandidates(final IdentifiedObject object) throws
FactoryException {
+         final TableInfo   table;                        // Contains `codeColumn` and `table`
names.
+         final Condition[] filters;                      // Conditions to put in the WHERE
clause.
+ crs:    if (isInstance(CoordinateReferenceSystem.class, object)) {
+             /*
+              * For compound CRS, the SQL statement may be something like below
+              *
+              *   SELECT COORD_REF_SYS_CODE FROM "Coordinate Reference System"
+              *     WHERE CAST(COORD_REF_SYS_KIND AS VARCHAR(80)) LIKE 'compound%'
+              *       AND CMPD_HORIZCRS_CODE IN (?,…)
+              *       AND CMPD_VERTCRS_CODE IN (?,…)
+              */
+             table = TableInfo.CRS;
+             if (isInstance(CompoundCRS.class, object)) {
+                 final List<CoordinateReferenceSystem> components = ((CompoundCRS)
object).getComponents();
+                 if (components != null) switch (components.size()) {        // Paranoiac
check.
+                     case 1: return getCodeCandidates(components.get(0));    // Should not
happen.
+                     case 2: {
+                         filters = new Condition[2];
+                         for (int i=0; i<=1; i++) {
+                             if ((filters[i] = dependencies((i==0) ? "CMPD_HORIZCRS_CODE"
: "CMPD_VERTCRS_CODE",
+                                     CoordinateReferenceSystem.class, components.get(i),
false)) == null)
+                             {
+                                 return Collections.emptySet();
+                             }
+                         }
+                         break crs;
+                     }
+                 }
+             }
+             /*
+              * For Coordinate Reference System, the SQL statement may be something like
below
+              * (with DATUM_CODE replaced by SOURCE_GEOGCRS_CODE in a projected CRS):
+              *
+              *   SELECT COORD_REF_SYS_CODE FROM "Coordinate Reference System"
+              *     WHERE CAST(COORD_REF_SYS_KIND AS VARCHAR(80)) LIKE 'geographic%'
+              *       AND DATUM_CODE IN (?,…) AND DEPRECATED=0
+              *     ORDER BY COORD_REF_SYS_CODE
+              */
+             final Condition filter;
+             if (object instanceof GeneralDerivedCRS) {              // No need to use isInstance(Class,
Object) from here.
 -                filter = dependencies("SOURCE_GEOGCRS_CODE", SingleCRS.class, ((GeneralDerivedCRS)
object).getBaseCRS(), true);
++                filter = dependencies("SOURCE_GEOGCRS_CODE", CoordinateReferenceSystem.class,
((GeneralDerivedCRS) object).getBaseCRS(), true);
+             } else if (object instanceof GeodeticCRS) {
+                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, ((GeodeticCRS)
object).getDatum(), true);
+             } else if (object instanceof VerticalCRS) {
+                 filter = dependencies("DATUM_CODE", VerticalDatum.class, ((VerticalCRS)
object).getDatum(), true);
+             } else if (object instanceof TemporalCRS) {
+                 filter = dependencies("DATUM_CODE", TemporalDatum.class, ((TemporalCRS)
object).getDatum(), true);
+             } else if (object instanceof SingleCRS) {
+                 filter = dependencies("DATUM_CODE", Datum.class, ((SingleCRS) object).getDatum(),
true);
+             } else {
+                 return Collections.emptySet();
+             }
+             if (filter == null) {
+                 return Collections.emptySet();
+             }
+             filters = new Condition[] {filter};
+         } else if (isInstance(Datum.class, object)) {
+             /*
+              * We currently have no better way to filter datum (or reference frames) than
their names.
+              * Filtering must be at least as tolerant as AbstractDatum.isHeuristicMatchForName(String).
+              * The SQL statement will be something like below:
+              *
+              *   SELECT DATUM_CODE FROM "Datum"
+              *    WHERE ELLIPSOID_CODE IN (?,…)
+              *      AND (LOWER(DATUM_NAME) LIKE '?%')
+              */
+             table = TableInfo.DATUM;
+             if (isInstance(GeodeticDatum.class, object)) {
+                 filters = new Condition[] {
+                     dependencies("ELLIPSOID_CODE", Ellipsoid.class, ((GeodeticDatum) object).getEllipsoid(),
true),
+                     Condition.NAME
+                 };
+                 if (filters[0] == null) {
+                     return Collections.emptySet();
+                 }
+             } else {
+                 if (isInstance(VerticalDatum.class, object)) {
+                     final VerticalDatumType type = ((VerticalDatum) object).getVerticalDatumType();
+                     if (type != null && !type.equals(EPSGDataAccess.VERTICAL_DATUM_TYPE))
{
+                         return Collections.emptySet();
+                     }
+                 }
+                 filters = new Condition[] {
+                     Condition.NAME
+                 };
+             }
+         } else if (isInstance(Ellipsoid.class, object)) {
+             /*
+              * The SQL query will be something like below:
+              *
+              *   SELECT ELLIPSOID_CODE FROM "Ellipsoid"
+              *     WHERE SEMI_MAJOR_AXIS >= ?-ε AND SEMI_MAJOR_AXIS <= ?+ε
+              *     ORDER BY ABS(SEMI_MAJOR_AXIS-?)
+              */
+             table   = TableInfo.ELLIPSOID;
+             filters = new Condition[] {
+                 new FloatCondition("SEMI_MAJOR_AXIS", ((Ellipsoid) object).getSemiMajorAxis())
+             };
+         } else {
+             // Not a supported type. Returns all codes.
+             return super.getCodeCandidates(object);
+         }
+         /*
+          * At this point we collected the information needed for creating the main SQL query.
+          * If the filters include a filter by names, we will need to take aliases in account.
+          * The following block prepares in advance the SQL query that we will need to execute,
+          * but does not execute it now. Note that this block overwrites the `buffer` content,
+          * so that buffer shall not contain valuable information yet.
+          */
+         final StringBuilder buffer = new StringBuilder(350);     // Temporary buffer for
building SQL query.
+         final Set<String> namePatterns;
+         final String aliasSQL;
+         if (ArraysExt.containsIdentity(filters, Condition.NAME)) {
+             namePatterns = new LinkedHashSet<>();
+             namePatterns.add(toDatumPattern(object.getName().getCode(), buffer));
+             for (final GenericName id : object.getAlias()) {
+                 namePatterns.add(toDatumPattern(id.tip().toString(), buffer));
+             }
+             buffer.setLength(0);
+             buffer.append("SELECT OBJECT_CODE FROM [Alias] WHERE OBJECT_TABLE_NAME='").append(table.unquoted()).append("'
AND ");
+             // PostgreSQL does not require explicit cast when the value is a literal instead
than "?".
+             appendFilterByName(namePatterns, "ALIAS", buffer);
+             aliasSQL = dao.translator.apply(buffer.toString());
+             buffer.setLength(0);
+         } else {
+             namePatterns = null;
+             aliasSQL = null;
+         }
+         /*
+          * Prepare the first part of SQL statement, which may be like below:
+          *
+          *    SELECT <codeColumn> FROM <table>
+          *      WHERE CAST(<typeColumn> AS VARCHAR(80)) LIKE 'type%'
+          *        AND <filter.column> IN (<filter.values>)
+          *        AND (LOWER(<nameColumn>) LIKE '<name>%')
+          *
+          * The query is assembled in the `buffer`. The first WHERE condition specifies the
desired type.
+          * That condition may be absent. The next conditions specify desired values. It
may be EPSG codes
+          * of dependencies or parameter values as floating points. The last condition is
on the object name.
+          * It may be absent (typically, only datums or reference frames have that condition).
+          */
+         buffer.append("SELECT ").append(table.codeColumn).append(" FROM ").append(table.table);
+         table.where(object.getClass(), buffer);         // Unconditionally append a "WHERE"
clause.
+         boolean isNext = false;
+         for (final Condition filter : filters) {
+             isNext |= filter.appendToWhere(buffer, isNext);
+         }
+         /*
+          * We did not finished to build the SQL query, but the remaining part may require
a JDBC connection.
+          * We do not use PreparedStatement because the number of parameters varies, and
we may need to use a
+          * Statement two times for completely different queries.
+          */
+         try (Statement stmt = dao.connection.createStatement()) {
+             if (namePatterns != null) {
+                 if (isNext) buffer.append(" AND ");
+                 isNext = false;
+                 appendFilterByName(namePatterns, table.nameColumn, buffer);
+                 try (ResultSet result = stmt.executeQuery(aliasSQL)) {
+                     while (result.next()) {
+                         final int code = result.getInt(1);
+                         if (!result.wasNull()) {            // Should never be null but
we are paranoiac.
+                             if (!isNext) {
+                                 isNext = true;
+                                 buffer.append(" OR ").append(table.codeColumn).append("
IN (");
+                             } else {
+                                 buffer.append(',');
+                             }
+                             buffer.append(code);
+                         }
+                     }
+                 }
+                 if (isNext) buffer.append(')');
+             }
+             buffer.append(getSearchDomain() == Domain.ALL_DATASET
+                           ? " ORDER BY ABS(DEPRECATED), "
+                           : " AND DEPRECATED=0 ORDER BY ");     // Do not put spaces around
"=" - SQLTranslator searches for this exact match.
+             for (final Condition filter : filters) {
+                 filter.appendToOrderBy(buffer);
+             }
+             buffer.append(table.codeColumn);          // Only for making order determinist.
+             /*
+              * At this point the SQL query is complete. Run it, preserving order.
+              * Then sort the result by taking in account the supersession table.
+              */
+             final Set<String> result = new LinkedHashSet<>();       // We need
to preserve order in this set.
+             try (ResultSet r = stmt.executeQuery(dao.translator.apply(buffer.toString())))
{
+                 while (r.next()) {
+                     result.add(r.getString(1));
+                 }
+             }
+             result.remove(null);                    // Should not have null element, but
let be safe.
+             if (result.size() > 1) {
+                 final Object[] id = result.toArray();
+                 if (dao.sort(table.unquoted(), id)) {
+                     result.clear();
+                     for (final Object c : id) {
+                         result.add((String) c);
+                     }
+                 }
+             }
+             return result;
+         } catch (SQLException exception) {
+             throw dao.databaseFailure(Identifier.class, String.valueOf(CollectionsExt.first(filters[0].values)),
exception);
+         }
+     }
+ 
+     /**
+      * Returns a SQL pattern for the given datum name. The name is returned in all lower
cases for allowing
+      * case-insensitive searches. Punctuations are replaced by any sequence of characters
({@code '%'}) and
+      * non-ASCII letters are replaced by any single character ({@code '_'}). The returned
pattern should be
+      * flexible enough for accepting all names considered equal in {@code DefaultGeodeticDatum}
comparisons.
+      * In case of doubt, it is okay to return a pattern accepting more names.
+      *
+      * @param  name    the datum name for which to return a SQL pattern.
+      * @param  buffer  temporary buffer to use for creating the pattern.
+      * @return the SQL pattern for the given name.
+      *
+      * @see org.apache.sis.referencing.datum.DefaultGeodeticDatum#isHeuristicMatchForName(String)
+      */
+     private static String toDatumPattern(final String name, final StringBuilder buffer)
{
+         int start = 0;
+         if (name.startsWith(ESRI_DATUM_PREFIX)) {
+             start = ESRI_DATUM_PREFIX.length();
+         }
+         int end = name.indexOf('(');        // Ignore "Paris" in "Nouvelle Triangulation
Française (Paris)".
+         if (end < 0) end = name.length();
+         end = CharSequences.skipTrailingWhitespaces(name, start, end);
+         buffer.setLength(0);
+         SQLUtilities.toLikePattern(name, start, end, true, true, buffer);
+         return buffer.toString();
+     }
+ 
+     /**
+      * Appends to the given buffer the SQL statement for filtering datum names using a pattern
created by
+      * {@link #toDatumPattern(String, StringBuilder)}. This method append a SQL fragment
like below:
+      *
+      * {@preformat sql
+      *     (LOWER(<column>) LIKE '<pattern>' OR …)
+      * }
+      *
+      * This method assumes that {@code namePatterns} contains at least one element.
+      *
+      * @param  namePatterns  the patterns created by {@link #toDatumPattern(String, StringBuilder)}.
+      * @param  column        column where the search for the names.
+      * @param  buffer        buffer where to add the SQL fragment.
+      */
+     private static void appendFilterByName(final Set<String> namePatterns, final String
column, final StringBuilder buffer) {
+         String separator = "(";
+         for (final String pattern : namePatterns) {
+             buffer.append(separator).append("LOWER(").append(column)
+                   .append(") LIKE '").append(pattern).append('\'');
+             separator = " OR ";
+         }
+         buffer.append(')');
+     }
+ }


Mime
View raw message