You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/11/15 09:23:26 UTC

[sis] 03/04: If the unmarshalled operation in is a defining conversion, complete the conversion.

This is an automated email from the ASF dual-hosted git repository.

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

commit a7d1c7e0a3ce4b1b1f746df133571a2d9d02ddc4
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Oct 31 12:35:59 2022 +0100

    If the unmarshalled operation in <gml:PassThroughOperation> is a defining conversion, complete the conversion.
---
 .../referencing/CC_GeneralOperationParameter.java  |  10 +-
 .../jaxb/referencing/CC_GeneralParameterValue.java |   2 +-
 .../jaxb/referencing/CC_OperationMethod.java       |   2 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  13 +--
 .../operation/AbstractCoordinateOperation.java     |  20 +---
 .../operation/AbstractSingleOperation.java         |   3 +-
 .../referencing/operation/DefaultConversion.java   |  18 ++--
 .../operation/DefaultPassThroughOperation.java     | 116 +++++++++++++++------
 .../apache/sis/referencing/operation/SubTypes.java |   2 +-
 9 files changed, 112 insertions(+), 74 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
index bc58625c90..f99929afff 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
@@ -171,12 +171,14 @@ public final class CC_GeneralOperationParameter extends PropertyType<CC_GeneralO
      * to occur in some arbitrary place.
      *
      * @param  descriptor  the descriptor to validate.
+     * @param  parent      the name of the element to report as the parent of {@code property}.
      * @param  property    the name of the property to report as missing if an exception is thrown.
      * @throws GeodeticException if the parameters are missing or invalid.
      */
-    static void validate(final GeneralParameterDescriptor descriptor, String property) {
+    static void validate(final GeneralParameterDescriptor descriptor, final String parent, final String property) {
         if (descriptor == null || descriptor.getName() == null) {
-            short key = Errors.Keys.MissingValueForProperty_1;
+            short key = Errors.Keys.MissingComponentInElement_2;
+            String[] args = {parent, property};
             /*
              * The exception thrown by this method must be unchecked,
              * otherwise JAXB just reports is without propagating it.
@@ -185,10 +187,10 @@ public final class CC_GeneralOperationParameter extends PropertyType<CC_GeneralO
                 final String link = ((IdentifiedObject) descriptor).getIdentifierMap().get(IdentifierSpace.XLINK);
                 if (link != null) {
                     key = Errors.Keys.NotABackwardReference_1;
-                    property = link;
+                    args = new String[] {link};
                 }
             }
-            throw new GeodeticException(Errors.format(key, property));
+            throw new GeodeticException(Errors.format(key, args));
         }
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralParameterValue.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralParameterValue.java
index ba45ef344c..b52c4952ff 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralParameterValue.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralParameterValue.java
@@ -110,6 +110,6 @@ public final class CC_GeneralParameterValue extends PropertyType<CC_GeneralParam
      */
     public void setElement(final GeneralParameterValue parameter) {
         metadata = parameter;
-        CC_GeneralOperationParameter.validate(parameter.getDescriptor(), "<gml:operationParameter>");
+        CC_GeneralOperationParameter.validate(parameter.getDescriptor(), "ParameterValue", "operationParameter");
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationMethod.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationMethod.java
index 6eb815ffad..ed050b78ba 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationMethod.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationMethod.java
@@ -108,7 +108,7 @@ public final class CC_OperationMethod extends PropertyType<CC_OperationMethod, O
      */
     public void setElement(final DefaultOperationMethod method) {
         metadata = method;
-        CC_GeneralOperationParameter.validate(method.getParameters(), "<gml:parameter>");
+        CC_GeneralOperationParameter.validate(method.getParameters(), "OperationMethod", "parameter");
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractDerivedCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
index e4547697b6..e3b6407963 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
@@ -22,7 +22,6 @@ import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlSeeAlso;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.ValidationException;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.SingleCRS;
@@ -34,12 +33,12 @@ import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.geometry.MismatchedDimensionException;
+import org.apache.sis.referencing.GeodeticException;
 import org.apache.sis.referencing.operation.DefaultConversion;
 import org.apache.sis.internal.jaxb.referencing.CC_Conversion;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 import org.apache.sis.internal.metadata.ImplementationHelper;
 import org.apache.sis.internal.metadata.Identifiers;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Semaphores;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -54,7 +53,7 @@ import static org.apache.sis.util.Utilities.deepEquals;
  * (not by a {@linkplain org.apache.sis.referencing.datum.AbstractDatum datum}).
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  *
  * @param <C>  the conversion type, either {@code Conversion} or {@code Projection}.
  *
@@ -82,6 +81,7 @@ abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS impl
      *
      * @see #getConversionFromBase()
      */
+    @SuppressWarnings("serial")         // Not statically typed as Serializable.
     private C conversionFromBase;
 
     /**
@@ -162,9 +162,6 @@ abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS impl
         if (properties != null) {
             factory = (MathTransformFactory) properties.get(ReferencingFactoryContainer.MT_FACTORY);
         }
-        if (factory == null) {
-            factory = DefaultFactories.forBuildin(MathTransformFactory.class);
-        }
         try {
             return DefaultConversion.castOrCopy(conversion).specialize(getConversionType(), baseCRS, this, factory);
         } catch (FactoryException e) {
@@ -332,7 +329,7 @@ abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS impl
      * coordinate system (CS). The CS information is required by {@code createConversionFromBase(…)}
      * in order to create a {@link MathTransform} with correct axis swapping and unit conversions.
      */
-    private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) throws ValidationException {
+    private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
         String property = "conversion";
         if (conversionFromBase != null) {
             final SingleCRS baseCRS = CC_Conversion.setBaseCRS(conversionFromBase, null);  // Clear the temporary value now.
@@ -350,6 +347,6 @@ abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS impl
          * and call to `getConversionFromBase()` will throw a ClassCastException if this instance is actually
          * a ProjectedCRS (because of the method overriding with return type covariance).
          */
-        throw new ValidationException(Identifiers.missingValueForProperty(getName(), property));
+        throw new GeodeticException(Identifiers.missingValueForProperty(getName(), property));
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 589e5f7781..f7db0369b3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -670,23 +670,8 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
     }
 
     /**
-     * Returns the object for transforming coordinates in the {@linkplain #getSourceCRS() source CRS}
-     * to coordinates in the {@linkplain #getTargetCRS() target CRS}.
-     *
-     * <h4>Use with interpolation CRS</h4>
-     * If the {@linkplain #getInterpolationCRS() interpolation CRS} is non-null, then the math transform
-     * input coordinates shall by (<var>interpolation</var>, <var>source</var>) tuples: for each value
-     * to transform, the interpolation point coordinates shall be first, followed by the source coordinates.
-     *
-     * <div class="note"><b>Example:</b>
-     * in a transformation between two {@linkplain org.apache.sis.referencing.crs.DefaultVerticalCRS vertical CRS},
-     * if the {@linkplain #getSourceCRS() source} coordinates are (<var>z</var>) values but the coordinate operation
-     * additionally requires (<var>x</var>,<var>y</var>) values for {@linkplain #getInterpolationCRS() interpolation}
-     * purpose, then the math transform input coordinates shall be (<var>x</var>,<var>y</var>,<var>z</var>) tuples in
-     * that order.</div>
-     *
-     * The interpolation coordinates will {@linkplain DefaultPassThroughOperation pass through the operation}
-     * and appear in the math transform outputs, in the same order than inputs.
+     * Returns the object for transforming coordinates in the source CRS to coordinates in the target CRS.
+     * The transform may be {@code null} if this coordinate operation is a defining conversion.
      *
      * @return the transform from source to target CRS, or {@code null} if not applicable.
      */
@@ -1208,6 +1193,7 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
 
     /**
      * Invoked by JAXB after unmarshalling.
+     * May be overridden by subclasses.
      */
     void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
         computeTransientFields();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
index 8d89447e1c..6fa4250a43 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
@@ -49,6 +49,7 @@ import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.metadata.ImplementationHelper;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.metadata.Identifiers;
+import org.apache.sis.referencing.GeodeticException;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
@@ -408,7 +409,7 @@ class AbstractSingleOperation extends AbstractCoordinateOperation implements Sin
     private void setParameters(final GeneralParameterValue[] values) {
         if (parameters == null) {
             if (!(method instanceof DefaultOperationMethod)) {  // May be a non-null proxy if defined only by xlink:href.
-                throw new IllegalStateException(Identifiers.missingValueForProperty(getName(), "method"));
+                throw new GeodeticException(Identifiers.missingValueForProperty(getName(), "method"));
             }
             /*
              * The descriptors in the <gml:method> element do not know the class of parameter value
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java
index 3fff4153d9..1732f7c091 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java
@@ -36,6 +36,7 @@ import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactor
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Utilities;
@@ -217,8 +218,8 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
 
     /**
      * Constructs a new conversion with the same values than the specified one, together with the
-     * specified source and target CRS. While the source conversion can be an arbitrary one, it is
-     * typically a defining conversion.
+     * specified source and target CRS. While the source conversion can be an arbitrary one,
+     * it is typically a defining conversion.
      *
      * @param definition  the defining conversion.
      * @param source      the new source CRS.
@@ -376,13 +377,14 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
      *
      * This {@code specialize(…)} method returns a conversion which implement at least the given {@code baseType}
      * interface, but may also implement a more specific GeoAPI interface if {@code specialize(…)} has been able
-     * to infer the type from this operation {@linkplain #getMethod() method}.
+     * to infer the type from the {@linkplain #getMethod() operation method}.
      *
      * @param  <T>        compile-time type of the {@code baseType} argument.
      * @param  baseType   the base GeoAPI interface to be implemented by the conversion to return.
      * @param  sourceCRS  the source CRS.
      * @param  targetCRS  the target CRS.
-     * @param  factory    the factory to use for creating a transform from the parameters or for performing axis changes.
+     * @param  factory    the factory to use for creating a transform from the parameters or for performing axis changes,
+     *                    or {@code null} for the default factory.
      * @return the conversion of the given type between the given CRS.
      * @throws ClassCastException if a contradiction is found between the given {@code baseType},
      *         the defining {@linkplain DefaultConversion#getInterface() conversion type} and
@@ -397,12 +399,11 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
      */
     public <T extends Conversion> T specialize(final Class<T> baseType,
             final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS,
-            final MathTransformFactory factory) throws FactoryException
+            MathTransformFactory factory) throws FactoryException
     {
         ArgumentChecks.ensureNonNull("baseType",  baseType);
         ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
         ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
-        ArgumentChecks.ensureNonNull("factory",   factory);
         /*
          * Conceptual consistency check: verify that the new CRS use the same datum than the previous ones,
          * since the purpose of this method is not to apply datum changes. Datum changes are the purpose of
@@ -425,6 +426,9 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
                 ensureCompatibleDatum("targetCRS", sourceCRS, super.getTargetCRS());
             }
         }
+        if (factory == null) {
+            factory = DefaultFactories.forBuildin(MathTransformFactory.class);
+        }
         return SubTypes.create(baseType, this, sourceCRS, targetCRS, factory);
     }
 
@@ -505,7 +509,7 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
     /**
      * Constructs a new object in which every attributes are set to a null value.
      * <strong>This is not a valid object.</strong> This constructor is strictly
-     * reserved to JAXB, which will assign values to the fields using reflexion.
+     * reserved to JAXB, which will assign values to the fields using reflection.
      */
     private DefaultConversion() {
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
index 411ea47aab..60749dbaa6 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
@@ -19,36 +19,43 @@ package org.apache.sis.referencing.operation;
 import java.util.Map;
 import java.util.Arrays;
 import java.util.Objects;
+import javax.xml.bind.Unmarshaller;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.PassThroughOperation;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CompoundCRS;
+import org.apache.sis.referencing.GeodeticException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.metadata.ImplementationHelper;
 import org.apache.sis.util.UnsupportedImplementationException;
-import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.referencing.CRS;
 
 import static org.apache.sis.util.Utilities.deepEquals;
+import org.opengis.referencing.operation.Conversion;
 
+// Branch-dependent imports
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.SingleOperation;
 
+
 /**
  * Specifies that a subset of a coordinate tuple is subject to a specific coordinate operation.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 1.3
  * @since   0.6
  * @module
  */
@@ -114,9 +121,8 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
                                        final int firstAffectedCoordinate,
                                        final int numTrailingCoordinates)
     {
-        super(properties, sourceCRS, targetCRS, null, MathTransforms.passThrough(
-                firstAffectedCoordinate, operation.getMathTransform(), numTrailingCoordinates));
-        ArgumentChecks.ensureNonNull("operation", operation);
+        super(properties, sourceCRS, targetCRS, null,
+              MathTransforms.passThrough(firstAffectedCoordinate, operation.getMathTransform(), numTrailingCoordinates));
         this.operation = operation;
     }
 
@@ -223,11 +229,11 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
         final MathTransform transform = super.getMathTransform();
         if (transform instanceof PassThroughTransform) {
             return ((PassThroughTransform) transform).getModifiedCoordinates();
-        } else {
+        } else if (operation != null) {
             /*
-             * Should not happen with objects created by public methods since the constructor created the transform
-             * itself. However may happen with operations parsed from GML. As a fallback, search in the components
-             * of CompoundCRS. This is not a universal fallback, but work for the most straightforward cases.
+             * Should not happen with objects created by public methods since the constructor created the transform itself.
+             * However may happen with operations parsed from GML. As a fallback, search in the components of CompoundCRS.
+             * This is not a universal fallback, but works for the most straightforward cases.
              */
             final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
             if (sourceCRS instanceof CompoundCRS) {
@@ -245,8 +251,8 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
                     firstAffectedCoordinate += dim;
                 }
             }
-            throw new UnsupportedImplementationException(transform.getClass());
         }
+        throw new UnsupportedImplementationException(transform.getClass());
     }
 
     /**
@@ -326,13 +332,13 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
     /**
      * Constructs a new object in which every attributes are set to a null value.
      * <strong>This is not a valid object.</strong> This constructor is strictly
-     * reserved to JAXB, which will assign values to the fields using reflexion.
+     * reserved to JAXB, which will assign values to the fields using reflection.
      */
     private DefaultPassThroughOperation() {
         /*
          * A sub-operation is mandatory for SIS working. We do not verify its presence here because the verification
-         * would have to be done in an 'afterMarshal(…)' method and throwing an exception in that method causes the
-         * whole unmarshalling to fail. But the CC_CoordinateOperation adapter does some verifications.
+         * would have to be done in an `afterMarshal(…)` method and throwing an exception in that method causes the
+         * whole unmarshalling to fail. But the `CC_CoordinateOperation` adapter does some verifications.
          */
     }
 
@@ -358,37 +364,79 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
      */
     @XmlElement(name = "modifiedCoordinate", required = true)
     private int[] getIndices() {
-        final int[] indices = getModifiedCoordinates();
-        for (int i=0; i<indices.length; i++) {
-            indices[i]++;
+        final int[] dimensions = getModifiedCoordinates();
+        for (int i=0; i<dimensions.length; i++) {
+            dimensions[i]++;
         }
-        return indices;
+        return dimensions;
     }
 
     /**
      * Invoked by JAXB at unmarshalling time for setting the modified coordinates.
+     * This method needs to be invoked last, even if the {@code <gml:modifiedCoordinate>}
+     * elements are not last in the GML document. It is the case when using JAXB because
+     * multiple occurrences of {@code <gml:modifiedCoordinate>} are aggregated in an array.
      */
-    private void setIndices(final int[] coordinates) {
-        String missing = "sourceCRS";
-        final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
-        if (sourceCRS != null) {
-            missing = "modifiedCoordinate";
-            if (coordinates != null && coordinates.length != 0) {
-                missing = "coordOperation";
-                if (operation != null) {
-                    for (int i=1; i<coordinates.length; i++) {
-                        final int previous = coordinates[i-1];
-                        if (previous < 1 || coordinates[i] != previous + 1) {
-                            throw new IllegalArgumentException(Errors.format(
-                                    Errors.Keys.CanNotAssign_2, missing, Arrays.toString(coordinates)));
+    private void setIndices(final int[] dimensions) {
+        /*
+         * Argument and state validation.
+         */
+        String missing = "modifiedCoordinate";
+        FactoryException cause = null;
+        final int n = dimensions.length;
+        if (n != 0) {
+            if (!ArraysExt.isRange(dimensions[0], dimensions))  {
+                throw new GeodeticException(Errors.format(Errors.Keys.CanNotAssign_2, missing, Arrays.toString(dimensions)));
+            }
+            missing = "sourceCRS";
+            final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
+            if (sourceCRS != null) {
+                missing = "targetCRS";
+                final CoordinateReferenceSystem targetCRS = super.getTargetCRS();
+                if (targetCRS != null) {
+                    missing = "coordOperation";
+                    if (operation != null) {
+                        /*
+                         * If the operation is a defining operation, we need to replace it by a full operation.
+                         * After that, we can store the modified coordinate indices in the transform field.
+                         */
+                        MathTransform subTransform = operation.getMathTransform();
+                        if (operation instanceof Conversion) {
+                            CoordinateReferenceSystem sourceSub = operation.getSourceCRS();
+                            CoordinateReferenceSystem targetSub = operation.getTargetCRS();
+                            if (subTransform == null || sourceSub == null || targetSub == null) try {
+                                final int[] zeroBased = dimensions.clone();
+                                for (int i=0; i<n; i++) zeroBased[i]--;
+                                if (sourceSub == null) sourceSub = CRS.selectDimensions(sourceCRS, zeroBased);
+                                if (targetSub == null) targetSub = CRS.selectDimensions(targetCRS, zeroBased);
+                                operation = DefaultConversion.castOrCopy((Conversion) operation)
+                                            .specialize(Conversion.class, sourceSub, targetSub, null);
+                                subTransform = operation.getMathTransform();
+                            } catch (FactoryException e) {
+                                cause = e;
+                            }
+                        }
+                        if (subTransform != null) {
+                            transform = MathTransforms.passThrough(dimensions[0] - 1, subTransform,
+                                    ReferencingUtilities.getDimension(sourceCRS) - dimensions[n-1]);
+                            return;
                         }
                     }
-                    transform = MathTransforms.passThrough(coordinates[0] - 1, operation.getMathTransform(),
-                            ReferencingUtilities.getDimension(sourceCRS) - coordinates[coordinates.length - 1]);
-                    return;
                 }
             }
         }
-        throw new IllegalStateException(Errors.format(Errors.Keys.MissingComponentInElement_2, missing, "PassThroughOperation"));
+        throw new GeodeticException(Errors.format(Errors.Keys.MissingComponentInElement_2, "PassThroughOperation", missing), cause);
+    }
+
+    /**
+     * Invoked by JAXB after unmarshalling. If needed, this method tries to infer source/target CRS
+     * of the nested operation from the source/target CRS if the enclosing pass-through operation.
+     */
+    @Override
+    void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+        super.afterUnmarshal(unmarshaller, parent);
+        if (transform == null) {
+            setIndices(ArraysExt.EMPTY_INT);        // Cause an exception to be thrown.
+        }
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubTypes.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubTypes.java
index f525f8d809..90670bad4b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubTypes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubTypes.java
@@ -179,7 +179,7 @@ final class SubTypes {
                     conversion = new DefaultConversion(definition, sourceCRS, targetCRS, factory, actual);
                 }
                 /*
-                 * The DefaultConversion constructor may have used by MathTransformFactory for creating the actual
+                 * The DefaultConversion constructor may have used MathTransformFactory for creating the actual
                  * MathTransform object. In such case, we can use the knownledge that the factory has about the
                  * coordinate operation for refining again the type of the object to be returned.
                  */