sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Allow `getAuthorityCodes(Class)` to filter by WKT keywords. Build the code list under write lock. Cache results.
Date Wed, 11 Nov 2020 16:06:47 GMT
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 dfb19c1  Allow `getAuthorityCodes(Class)` to filter by WKT keywords. Build the code
list under write lock. Cache results.
dfb19c1 is described below

commit dfb19c152eabfe09b190274a8becfe93f7db0705
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Nov 11 11:28:23 2020 +0100

    Allow `getAuthorityCodes(Class)` to filter by WKT keywords.
    Build the code list under write lock. Cache results.
---
 .../internal/referencing/ReferencingUtilities.java | 30 +++++---
 .../sis/internal/referencing/WKTKeywords.java      | 84 +++++++++++++++++++++-
 .../java/org/apache/sis/io/wkt/WKTDictionary.java  | 82 ++++++++++++++++++---
 .../apache/sis/referencing/GeodeticCalculator.java |  6 +-
 .../factory/GeodeticAuthorityFactory.java          |  2 +-
 .../apache/sis/referencing/operation/CRSPair.java  | 13 ++--
 .../operation/CoordinateOperationRegistry.java     | 17 ++---
 .../sis/internal/referencing/WKTKeywordsTest.java  | 41 ++++++++++-
 .../org/apache/sis/io/wkt/WKTDictionaryTest.java   | 14 +++-
 9 files changed, 240 insertions(+), 49 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
index 35e6745..03c02a2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
@@ -37,6 +37,7 @@ import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.VerticalDatumType;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
@@ -61,7 +62,7 @@ import static java.util.Collections.singletonMap;
  * <p><strong>Do not rely on this API!</strong> It may change in incompatible
way in any future release.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.5
  * @module
  */
@@ -147,20 +148,33 @@ public final class ReferencingUtilities extends Static {
      * Returns the GeoAPI interface implemented by the given object, or the implementation
class
      * if the interface is unknown.
      *
-     * @param  object  the object for which to get the GeoAPI interface, or {@code null}.
+     * @param  <T>       compile-time value of {@code baseType}.
+     * @param  baseType  parent interface of the desired type.
+     * @param  object    the object for which to get the GeoAPI interface, or {@code null}.
      * @return GeoAPI interface or implementation class of the given object, or {@code null}
if the given object is null.
      */
-    public static Class<?> getInterface(final IdentifiedObject object) {
-        if (object == null) {
-            return null;
-        } else if (object instanceof AbstractIdentifiedObject) {
-             return ((AbstractIdentifiedObject) object).getInterface();
+    public static <T extends IdentifiedObject> Class<? extends T> getInterface(final
Class<T> baseType, final T object) {
+        if (object instanceof AbstractIdentifiedObject) {
+             return ((AbstractIdentifiedObject) object).getInterface().asSubclass(baseType);
         } else {
-             return object.getClass();
+             return getInterface(baseType, Classes.getClass(object));
         }
     }
 
     /**
+     * Returns the GeoAPI interface implemented by the given class, or the class itself if
the interface is unknown.
+     *
+     * @param  <T>       compile-time value of {@code baseType}.
+     * @param  baseType  parent interface of the desired type.
+     * @param  type      type of object for which to get the GeoAPI interface, or {@code
null}.
+     * @return GeoAPI interface or implementation class, or {@code null} if the given type
is null.
+     */
+    public static <T extends IdentifiedObject> Class<? extends T> getInterface(final
Class<T> baseType, final Class<? extends T> type) {
+        final Class<? extends T>[] types = Classes.getLeafInterfaces(type, baseType);
+        return (types.length != 0) ? types[0] : type;
+    }
+
+    /**
      * Copies all {@link SingleCRS} components from the given source to the given collection.
      * For each {@link CompoundCRS} element found in the iteration, this method replaces
the
      * {@code CompoundCRS} by its {@linkplain CompoundCRS#getComponents() components}, which
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTKeywords.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTKeywords.java
index 241a6ce..aab1794 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTKeywords.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTKeywords.java
@@ -16,7 +16,10 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Map;
+import java.util.HashMap;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.ArraysExt;
 
 
 /**
@@ -30,12 +33,12 @@ import org.apache.sis.util.Static;
  * without affecting GML for instance.</p>
  *
  * <div class="note"><b>Note:</b>
- * this class should not contain any method or non-final constant, in order to avoid class
loading.
- * This class is intended to be used only at compile-time and could be omitted from the JAR
file.</div>
+ * all constants in this class are static and final. The Java compiler should replace those
constants
+ * by their literal values at compile time, which avoid the loading of this class at run-time.</div>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html">WKT 2 specification</a>
  * @see <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">Legacy
WKT 1</a>
@@ -220,4 +223,79 @@ public final class WKTKeywords extends Static {
      */
     public static final String
             Point       = "Point";
+
+    /**
+     * Mapping between types of object and WKT keywords. Each GeoAPI interfaces is associated
to one
+     * or many WKT keywords: new keywords defined by version 2 (sometime with synonymous)
and legacy
+     * keywords defined by WKT 1.
+     *
+     * @see #forType(Class)
+     */
+    private static final Map<Class<?>,String[]> TYPES = new HashMap<>(30);
+    static {
+        /*
+         * `SingleCRS` subtypes. The `GeodeticCRS` is a common parent of
+         * `GeographicCRS` and `GeocentricCRS`, repeated for both of them.
+         */
+        addType(org.opengis.referencing.crs.GeocentricCRS.class,  GeodeticCRS, GeodCRS, GeocCS);
+        addType(org.opengis.referencing.crs.GeographicCRS.class,  GeodeticCRS, GeodCRS, GeogCS);
+        String[][] subtypes;
+        subtypes = new String[][] {
+            addType(org.opengis.referencing.crs.GeodeticCRS.class,    GeodeticCRS,    GeodCRS,
GeocCS, GeogCS),
+            addType(org.opengis.referencing.crs.ProjectedCRS.class,   ProjectedCRS,   ProjCRS,
ProjCS),
+            addType(org.opengis.referencing.crs.VerticalCRS.class,    VerticalCRS,    VertCRS,
Vert_CS),
+            addType(org.opengis.referencing.crs.EngineeringCRS.class, EngineeringCRS, EngCRS,
 Local_CS),
+            addType(org.opengis.referencing.crs.DerivedCRS.class,     /* TODO: check ISO.
*/   Fitted_CS),
+            addType(org.opengis.referencing.crs.TemporalCRS.class,    TimeCRS)
+        };
+        /*
+         * `CoordinateReferenceSystem` subtypes: all `SingleCRS` + the `CompoundCRS`.
+         */
+        subtypes = new String[][] {
+            addType(org.opengis.referencing.crs.SingleCRS.class, ArraysExt.concatenate(subtypes)),
+            addType(org.opengis.referencing.crs.CompoundCRS.class, CompoundCRS, Compd_CS),
+        };
+        addType(org.opengis.referencing.crs.CoordinateReferenceSystem.class, ArraysExt.concatenate(subtypes));
+        /*
+         * `Datum` subtypes.
+         */
+        subtypes = new String[][] {
+            addType(org.opengis.referencing.datum.GeodeticDatum.class,    GeodeticDatum,
   Datum),
+            addType(org.opengis.referencing.datum.TemporalDatum.class,    TimeDatum,    
   TDatum),
+            addType(org.opengis.referencing.datum.VerticalDatum.class,    VerticalDatum,
   VDatum, Vert_Datum),
+            addType(org.opengis.referencing.datum.EngineeringDatum.class, EngineeringDatum,
EDatum, Local_Datum)
+        };
+        addType(org.opengis.referencing.datum.Datum.class, ArraysExt.concatenate(subtypes));
+        /*
+         * Other types having no common parent (ignoring `IdentifiedObject`).
+         */
+        addType(org.opengis.referencing.datum.Ellipsoid.class,              Ellipsoid, Spheroid);
+        addType(org.opengis.referencing.datum.PrimeMeridian.class,          PrimeMeridian,
PrimeM);
+        addType(org.opengis.referencing.cs.CoordinateSystemAxis.class,      Axis);
+        addType(org.apache.sis.referencing.datum.BursaWolfParameters.class, ToWGS84);
+        addType(org.opengis.referencing.operation.MathTransform.class,      Param_MT, Concat_MT,
Inverse_MT, PassThrough_MT);
+        addType(org.opengis.geometry.DirectPosition.class,                  Point);
+    }
+
+    /**
+     * Adds WKT keywords for the specified type.
+     */
+    private static String[] addType(final Class<?> type, final String... keywords)
{
+        if (TYPES.put(type, keywords) != null) {
+            throw new AssertionError(type);
+        }
+        return keywords;
+    }
+
+    /**
+     * Returns the WKT keywords for an object of the specified type.
+     * This method returns a direct reference to internal array;
+     * <strong>do not modify</strong>.
+     *
+     * @param  type  the GeoAPI interface of the element.
+     * @return the WKT keywords, or {@code null} if none or unknown.
+     */
+    public static String[] forType(final Class<?> type) {
+        return TYPES.get(type);
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
index 0f69a1f..62a0840 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.stream.Stream;
+import java.util.function.Predicate;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.io.LineNumberReader;
@@ -38,6 +39,7 @@ import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.FactoryDataException;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.internal.util.CollectionsExt;
@@ -47,9 +49,9 @@ import org.apache.sis.internal.jdk9.JDK9;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.collection.FrequencySortedSet;
 
 
@@ -115,6 +117,12 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
     private final Set<String> codespaces;
 
     /**
+     * Cache of authority codes computed by {@link #getAuthorityCodes(Class)}.
+     * This cache can be cleared at any time; values are recomputed when needed.
+     */
+    private final Map<Class<?>, Set<String>> codeCaches;
+
+    /**
      * The parser to use for creating geodetic objects from WKT definitions.
      * All uses of this parser shall be synchronized by the <code>{@linkplain #lock}.writeLock()</code>.
      */
@@ -295,13 +303,16 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
          *
          * @param  choices  end of a linked list of {@code Disambiguation}s.
          * @param  code     authority code (code space and version may vary).
+         * @param  filter   filter to apply of elements to add in the set.
          * @param  addTo    where to add the {@code codespace:version:code} tuples.
          *
          * @see WKTDictionary#getAuthorityCodes(Class)
          */
-        static void list(Disambiguation choices, final String code, final Set<String>
addTo) {
+        static void list(Disambiguation choices, final String code, final Predicate<Object>
filter, final Set<String> addTo) {
             do {
-                addTo.add(choices.identifier(code));
+                if (filter.test(choices.value)) {
+                    addTo.add(choices.identifier(code));
+                }
                 choices = choices.previous;
             } while (choices != null);
         }
@@ -349,6 +360,7 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
          * methods are not invoked.
          */
         definitions = new HashMap<>();
+        codeCaches  = new HashMap<>();
         codespaces  = new FrequencySortedSet<>(true);
         parser      = new WKTFormat(null, null);
         lock        = new ReentrantReadWriteLock();
@@ -361,6 +373,7 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
      * in all WKT strings. This method should be invoked after new WKTs have been added.
      */
     private void updateAuthority() {
+        codeCaches.clear();
         if (authorities != null) {
             String name = CollectionsExt.first(authorities);        // Most frequently declared
authority.
             if (name == null) {
@@ -842,6 +855,7 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
                     } else {
                         definitions.put(localCode, value);
                     }
+                    codeCaches.clear();
                     if (cause != null) {
                         throw new FactoryException(resources().getString(
                                 Resources.Keys.CanNotInstantiateGeodeticObject_1, code),
cause);
@@ -869,14 +883,60 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
      */
     @Override
     public Set<String> getAuthorityCodes(Class<? extends IdentifiedObject> type)
throws FactoryException {
-        final Set<String> codes = new HashSet<>(Containers.hashMapCapacity(definitions.size()));
-        for (final Map.Entry<String,Object> entry : definitions.entrySet()) {
-            final String code  = entry.getKey();
-            final Object value = entry.getValue();
-            if (value instanceof Disambiguation) {
-                Disambiguation.list((Disambiguation) value, code, codes);
-            } else {
-                codes.add(code);
+        ArgumentChecks.ensureNonNull("type", type);
+        if (!type.isInterface()) {
+            type = ReferencingUtilities.getInterface(IdentifiedObject.class, type);
+        }
+        Set<String> codes;
+        lock.readLock().lock();
+        try {
+            codes = codeCaches.get(type);
+        } finally {
+            lock.readLock().unlock();
+        }
+        if (codes == null) {
+            final String[] keywords = WKTKeywords.forType(type);
+            final Class<? extends IdentifiedObject> baseType = type;              
 // Because lambdas require final.
+            final Predicate<Object> filter = (element) -> {
+                if (element instanceof Element) {
+                    return (keywords == null) || ArraysExt.containsIgnoreCase(keywords, ((Element)
element).keyword);
+                } else {
+                    return baseType.isInstance(element);
+                }
+            };
+            lock.writeLock().lock();
+            try {
+                codes = codeCaches.get(type);                           // In case it has
been computed concurrently.
+                if (codes == null) {
+                    codes = new HashSet<>();
+                    for (final Map.Entry<String,Object> entry : definitions.entrySet())
{
+                        final String code  = entry.getKey();
+                        final Object value = entry.getValue();
+                        if (value instanceof Disambiguation) {
+                            Disambiguation.list((Disambiguation) value, code, filter, codes);
+                        } else if (filter.test(value)) {
+                            codes.add(code);
+                        }
+                    }
+                    /*
+                     * Verify if an existing collection (assigned to another type) provides
the same values.
+                     * If we find one, we will share the same instances.
+                     */
+                    boolean share = false;
+                    for (final Set<String> other : codeCaches.values()) {
+                        if (codes.equals(other)) {
+                            codes = other;
+                            share = true;
+                            break;
+                        }
+                    }
+                    if (!share) {
+                        codes = CollectionsExt.unmodifiableOrCopy(codes);
+                    }
+                    codeCaches.put(type, codes);
+                }
+            } finally {
+                lock.writeLock().unlock();
             }
         }
         return codes;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
index 4226f76..6161778 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
@@ -277,7 +277,8 @@ public class GeodeticCalculator {
     GeodeticCalculator(final CoordinateReferenceSystem crs, final Ellipsoid ellipsoid) {
         final GeographicCRS geographic = ReferencingUtilities.toNormalizedGeographicCRS(crs,
true, true);
         if (geographic == null) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
ReferencingUtilities.getInterface(crs)));
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
+                    ReferencingUtilities.getInterface(CoordinateReferenceSystem.class, crs)));
         }
         this.ellipsoid = ellipsoid;
         semiMajorAxis  = ellipsoid.getSemiMajorAxis();
@@ -301,7 +302,8 @@ public class GeodeticCalculator {
         ArgumentChecks.ensureNonNull("crs", crs);
         final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoid(crs);
         if (ellipsoid == null) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
ReferencingUtilities.getInterface(crs)));
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
+                    ReferencingUtilities.getInterface(CoordinateReferenceSystem.class, crs)));
         }
         if (ellipsoid.isSphere()) {
             return new GeodeticCalculator(crs, ellipsoid);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index 48726ab..a99f832 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -1308,7 +1308,7 @@ public abstract class GeodeticAuthorityFactory extends AbstractFactory
implement
          * Get the actual type of the object. Returns the GeoAPI type if possible,
          * or fallback on the implementation class otherwise.
          */
-        final Class<?> actual = ReferencingUtilities.getInterface(object);
+        final Class<?> actual = ReferencingUtilities.getInterface(IdentifiedObject.class,
object);
         /*
          * Get the authority from the object if possible, in order to avoid a call
          * to the potentially costly (for EPSGDataAccess) getAuthority() method.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
index 95f9cbc..e0cd5b7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
@@ -21,7 +21,7 @@ import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.referencing.AbstractIdentifiedObject;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.util.CharSequences;
@@ -33,7 +33,7 @@ import org.apache.sis.util.Classes;
  * Used as key in hash map.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -88,13 +88,8 @@ final class CRSPair {
         if (object == null) {
             return null;
         }
-        Class<? extends IdentifiedObject> type;
-        if (object instanceof AbstractIdentifiedObject) {
-            type = ((AbstractIdentifiedObject) object).getInterface();
-        } else {
-            type = Classes.getLeafInterfaces(object.getClass(), IdentifiedObject.class)[0];
-        }
-        String suffix, label = Classes.getShortName(type);
+        String suffix;
+        String label = Classes.getShortName(ReferencingUtilities.getInterface(IdentifiedObject.class,
object));
         if (label.endsWith((suffix = "CRS")) || label.endsWith(suffix = "CS")) {
             Object cs = object;
             if (object instanceof CoordinateReferenceSystem) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 0152e58..dcbcb97 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -49,7 +49,6 @@ import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -73,7 +72,6 @@ import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
-import org.apache.sis.util.Classes;
 import org.apache.sis.util.Deprecable;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.collection.Containers;
@@ -513,7 +511,7 @@ class CoordinateOperationRegistry {
                         final boolean inverse = Containers.isNullOrEmpty(authoritatives);
                         if (inverse) {
                             /*
-                             * No operation from 'source' to 'target' available. But maybe
there is an inverse operation.
+                             * No operation from `source` to `target` available. But maybe
there is an inverse operation.
                              * This is typically the case when the user wants to convert
from a projected to a geographic CRS.
                              * The EPSG database usually contains transformation paths for
geographic to projected CRS only.
                              */
@@ -869,20 +867,15 @@ class CoordinateOperationRegistry {
          * Determine whether the operation to create is a Conversion or a Transformation
          * (could also be a Conversion subtype like Projection, but this is less important).
          * We want the GeoAPI interface, not the implementation class.
-         * The most reliable way is to ask to the 'AbstractOperation.getInterface()' method,
+         * The most reliable way is to ask to the `AbstractOperation.getInterface()` method,
          * but this is SIS-specific. The fallback uses reflection.
          */
-        final Class<? extends IdentifiedObject> type;
-        if (operation instanceof AbstractIdentifiedObject) {
-             type = ((AbstractIdentifiedObject) operation).getInterface();
-        } else {
-             type = Classes.getLeafInterfaces(operation.getClass(), CoordinateOperation.class)[0];
-        }
-        properties.put(CoordinateOperations.OPERATION_TYPE_KEY, type);
+        properties.put(CoordinateOperations.OPERATION_TYPE_KEY,
+                ReferencingUtilities.getInterface(CoordinateOperation.class, operation));
         /*
          * Reuse the same operation method, but we may need to change its number of dimension.
          * The capability to resize an OperationMethod is specific to Apache SIS, so we must
-         * be prepared to see the 'redimension' call fails. In such case, we will try to
get
+         * be prepared to see the `redimension` call fails. In such case, we will try to
get
          * the SIS implementation of the operation method and try again.
          */
         if (operation instanceof SingleOperation) {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTKeywordsTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTKeywordsTest.java
index e6f296a..a54d91f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTKeywordsTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTKeywordsTest.java
@@ -16,8 +16,12 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Set;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import org.opengis.referencing.crs.*;
+import org.opengis.referencing.datum.*;
+import org.apache.sis.internal.jdk9.JDK9;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -28,7 +32,7 @@ import static org.junit.Assert.*;
  * Tests the {@link WKTKeywords} class.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
+ * @version 1.1
  * @since   0.6
  * @module
  */
@@ -48,10 +52,45 @@ public final strictfp class WKTKeywordsTest extends TestCase {
         for (final Field field : WKTKeywords.class.getDeclaredFields()) {
             final String name = field.getName();
             final int modifiers = field.getModifiers();
+            if (name.equals("TYPES")) {
+                assertFalse(name, Modifier.isPublic(modifiers));
+                continue;
+            }
             assertTrue(name, Modifier.isPublic(modifiers));
             assertTrue(name, Modifier.isStatic(modifiers));
             assertTrue(name, Modifier.isFinal (modifiers));
             assertEquals("As a policy of WKTKeywords, constants value should be equal to
field name.", name, field.get(null));
         }
     }
+
+    /**
+     * Verifies that {@link SingleCRS}, {@link CoordinateReferenceSystem} and {@link Datum}
base types
+     * contain all WKT keywords associated to subtypes.
+     */
+    @Test
+    public void verifyTypeHierarchy() {
+        verifyTypeHierarchy(SingleCRS.class, GeocentricCRS.class, GeographicCRS.class, ProjectedCRS.class,
+                            VerticalCRS.class, TemporalCRS.class, EngineeringCRS.class);
+        verifyTypeHierarchy(CoordinateReferenceSystem.class, SingleCRS.class, CompoundCRS.class,
+                            GeocentricCRS.class, GeographicCRS.class, ProjectedCRS.class,
+                            VerticalCRS.class, TemporalCRS.class, EngineeringCRS.class);
+        verifyTypeHierarchy(Datum.class, GeodeticDatum.class, VerticalDatum.class, TemporalDatum.class,
+                            EngineeringDatum.class);
+    }
+
+    /**
+     * Verify that the specified {@code base} type contain all WKT keywords associated to
specified subtypes.
+     */
+    @SafeVarargs
+    private static <T> void verifyTypeHierarchy(final Class<T> base, final Class<?
extends T>... subtypes) {
+        final Set<String> all = JDK9.setOf(WKTKeywords.forType(base));
+        assertNotNull(base.getName(), all);
+        for (final Class<? extends T> subtype : subtypes) {
+            final Set<String> specialized = JDK9.setOf(WKTKeywords.forType(subtype));
+            final String name = subtype.getName();
+            assertNotNull(name, specialized);
+            assertTrue(name, all.size() > specialized.size());
+            assertTrue(name, all.containsAll(specialized));
+        }
+    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
index 1e32651..5ae1678 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTDictionaryTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Set;
 import java.util.Arrays;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
@@ -24,7 +25,9 @@ import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -64,8 +67,15 @@ public final strictfp class WKTDictionaryTest extends TestCase {
          */
         assertArrayEquals("getCodeSpaces()", new String[] {"ESRI", "MyCodeSpace"}, factory.getCodeSpaces().toArray());
         assertSame("getAuthority()", Citations.ESRI, factory.getAuthority());
-        assertSetEquals(Arrays.asList("102018", "ESRI::102021", "MyCodeSpace::102021", "MyCodeSpace:v2:102021"),
-                factory.getAuthorityCodes(SingleCRS.class));
+        Set<String> codes = factory.getAuthorityCodes(IdentifiedObject.class);
+        assertSame(codes, factory.getAuthorityCodes(IdentifiedObject.class));       // Test
caching.
+        assertSame(codes, factory.getAuthorityCodes(SingleCRS.class));              // Test
sharing.
+        assertSetEquals(Arrays.asList("102018", "ESRI::102021", "MyCodeSpace::102021", "MyCodeSpace:v2:102021"),
codes);
+        assertSetEquals(Arrays.asList("102018", "ESRI::102021"), factory.getAuthorityCodes(ProjectedCRS.class));
+        codes = factory.getAuthorityCodes(GeographicCRS.class);
+        assertSetEquals(Arrays.asList("MyCodeSpace::102021", "MyCodeSpace:v2:102021"), codes);
+        assertSame(codes, factory.getAuthorityCodes(GeodeticCRS.class));            // Test
sharing.
+        assertSame(codes, factory.getAuthorityCodes(GeographicCRS.class));          // Test
caching.
         /*
          * Tests CRS creation.
          */


Mime
View raw message