| /* |
| * 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 javax.measure.Unit; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.cs.*; |
| import org.opengis.referencing.crs.*; |
| 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. The MS-Access dialect of SQL is assumed; |
| * it will be translated into ANSI SQL later by {@link SQLTranslator#apply(String)} if needed. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| */ |
| final class TableInfo { |
| /** |
| * The {@link EPSG} item used for coordinate reference systems. |
| */ |
| static final TableInfo CRS; |
| |
| /** |
| * The {@link EPSG} item used for datums. |
| */ |
| static final TableInfo DATUM; |
| |
| /** |
| * The {@link EPSG} item used for ellipsoids. |
| */ |
| static final TableInfo ELLIPSOID; |
| |
| /** |
| * List of tables and columns to test for 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(IdentifiedObject, StringBuilder)} method which |
| * will substitute implementation-neutral objects by the Apache SIS class. |
| */ |
| 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"), |
| /* |
| * 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), |
| |
| 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), |
| |
| 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), |
| |
| ELLIPSOID = new TableInfo(Ellipsoid.class, |
| "[Ellipsoid]", |
| "ELLIPSOID_CODE", |
| "ELLIPSOID_NAME", |
| null, null, null, null), |
| |
| new TableInfo(PrimeMeridian.class, |
| "[Prime Meridian]", |
| "PRIME_MERIDIAN_CODE", |
| "PRIME_MERIDIAN_NAME", |
| null, null, null, null), |
| |
| 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"), |
| |
| new TableInfo(OperationMethod.class, |
| "[Coordinate_Operation Method]", |
| "COORD_OP_METHOD_CODE", |
| "COORD_OP_METHOD_NAME", |
| null, null, null, null), |
| |
| new TableInfo(ParameterDescriptor.class, |
| "[Coordinate_Operation Parameter]", |
| "PARAMETER_CODE", |
| "PARAMETER_NAME", |
| null, null, null, null), |
| |
| new TableInfo(Unit.class, |
| "[Unit of Measure]", |
| "UOM_CODE", |
| "UNIT_OF_MEAS_NAME", |
| null, null, null, null), |
| }; |
| |
| /** |
| * The class of object to be created. |
| */ |
| final Class<?> type; |
| |
| /** |
| * The table name for SQL queries. May contains a {@code "JOIN"} clause. |
| * |
| * @see #unquoted() |
| */ |
| final String table; |
| |
| /** |
| * Column name for the code (usually with the {@code "_CODE"} suffix). |
| */ |
| final String codeColumn; |
| |
| /** |
| * Column name for the name (usually with the {@code "_NAME"} suffix), or {@code null}. |
| */ |
| final String nameColumn; |
| |
| /** |
| * Column type for the type (usually with the {@code "_TYPE"} suffix), or {@code null}. |
| * {@link EPSGDataAccess} and {@link AuthorityCodes} assumes that values in this column |
| * have the maximal length described in the {@value #ENUM_REPLACEMENT} statement. |
| */ |
| private final String typeColumn; |
| |
| /** |
| * The SQL type to use as a replacement for enumerated values on databases that do not support enumerations. |
| */ |
| static final String ENUM_REPLACEMENT = "VARCHAR(80)"; |
| |
| /** |
| * Sub-interfaces of {@link #type} to handle, or {@code null} if none. |
| */ |
| private final Class<?>[] subTypes; |
| |
| /** |
| * Names of {@link #subTypes} in the database, or {@code null} if none. |
| */ |
| private final String[] typeNames; |
| |
| /** |
| * The column that specify if the object should be shown, or {@code null} if none. |
| */ |
| final String showColumn; |
| |
| /** |
| * Stores information about a specific table. |
| */ |
| private TableInfo(final Class<?> type, |
| final String table, final String codeColumn, final String nameColumn, |
| final String typeColumn, final Class<?>[] subTypes, final String[] typeNames, |
| final String showColumn) |
| { |
| this.type = type; |
| this.table = table; |
| this.codeColumn = codeColumn; |
| this.nameColumn = nameColumn; |
| this.typeColumn = typeColumn; |
| this.subTypes = subTypes; |
| this.typeNames = typeNames; |
| this.showColumn = showColumn; |
| } |
| |
| /** |
| * Returns the table name without brackets. |
| */ |
| final String unquoted() { |
| return table.substring(1, table.length() - 1); |
| } |
| |
| /** |
| * Returns {@code true} if the given table {@code name} matches the {@code expected} name. |
| * The given {@code 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"}. |
| * |
| * <p>The table name should be one of the values enumerated in the {@code epsg_table_name} type of the |
| * {@code EPSG_Prepare.sql} file.</p> |
| * |
| * @param expected the expected table name (e.g. {@code "Coordinate_Operation"}). |
| * @param name the actual table name. |
| * @return whether the given {@code name} is considered to match the expected name. |
| */ |
| static boolean tableMatches(final String expected, String name) { |
| if (name == null) { |
| return false; |
| } |
| if (name.startsWith(SQLTranslator.TABLE_PREFIX)) { |
| name = name.substring(SQLTranslator.TABLE_PREFIX.length()); |
| } |
| return CharSequences.isAcronymForWords(name, expected); |
| } |
| |
| /** |
| * Appends a {@code WHERE} clause together with a condition for searching the specified object. |
| * This method delegates to {@link #where(Class, StringBuilder)} with the type of the given object, |
| * except that some object properties may be inspected for resolving ambiguities. |
| * |
| * @param object the object to search in the database. |
| * @param buffer where to append the {@code WHERE} clause. |
| */ |
| final void where(final IdentifiedObject object, final StringBuilder buffer) { |
| Class<?> userType = object.getClass(); |
| if (object instanceof GeodeticCRS) { |
| final CoordinateSystem cs = ((GeodeticCRS) object).getCoordinateSystem(); |
| if (cs instanceof EllipsoidalCS) { |
| userType = GeographicCRS.class; |
| } else if (cs instanceof CartesianCS || cs instanceof SphericalCS) { |
| userType = DefaultGeocentricCRS.class; |
| } |
| } |
| where(userType, buffer); |
| } |
| |
| /** |
| * Appends a {@code WHERE} clause together with a condition for searching the most specific subtype, |
| * if such condition can be added. The clause appended by this method looks like the following example |
| * (details may vary because of enumeration values): |
| * |
| * {@snippet lang="sql" : |
| * WHERE COORD_REF_SYS_KIND LIKE 'geographic%' AND |
| * } |
| * |
| * In any case, the caller shall add at least one condition after this method call. |
| * |
| * @param userType the type specified by the user. |
| * @param buffer where to append the {@code WHERE} clause. |
| * @return the subtype, or {@link #type} if no subtype was found. |
| */ |
| final Class<?> where(final Class<?> userType, final StringBuilder buffer) { |
| buffer.append(" WHERE "); |
| if (typeColumn != null) { |
| for (int i=0; i<subTypes.length; i++) { |
| final Class<?> candidate = subTypes[i]; |
| if (candidate.isAssignableFrom(userType)) { |
| if (ENUM_REPLACEMENT != null) { |
| buffer.append("CAST(").append(typeColumn).append(" AS ").append(ENUM_REPLACEMENT).append(')'); |
| } else { |
| buffer.append(typeColumn); |
| } |
| buffer.append(" LIKE '").append(typeNames[i]).append("%' AND "); |
| return candidate; |
| } |
| } |
| } |
| return type; |
| } |
| } |