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

commit f9060a68d408380d15cffa4c8387fd78a79080c4
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Sep 5 18:24:55 2022 +0200

    Parameters reported by `SingleOperation.getParameters()` sometime did not 
described what the `MathTransform` was really doing.
    Now when a correct set of parameters can not be easily determined, the 
parameter group is left unspecified (null reference).
    Parameters that can be inferred from the context should not be listed, 
because they may cause `InvalidParameterNameException`
    when those values are copied in a `ParameterValueGroup` that do not expect 
them (e.g. if trying to copy "semi_major" value
    in a group of parameters defined by EPSG database).
---
 .../provider/FranceGeocentricInterpolation.java    |   3 +-
 .../GeocentricAffineBetweenGeographic.java         |   2 +-
 .../provider/GeographicToGeocentric.java           |   2 +-
 .../internal/referencing/provider/Molodensky.java  |   2 +-
 .../sis/parameter/AbstractParameterDescriptor.java |   2 +-
 .../apache/sis/parameter/FilteredParameters.java   | 114 +++++++++++
 .../java/org/apache/sis/parameter/Parameters.java  |  43 +++++
 .../parameter/UnmodifiableParameterValueGroup.java |  21 ++-
 .../operation/AbstractSingleOperation.java         |  62 +++++-
 .../operation/CoordinateOperationFinder.java       |   7 +-
 .../operation/CoordinateOperationRegistry.java     |   7 +-
 .../referencing/operation/DefaultConversion.java   |  12 +-
 .../sis/referencing/operation/package-info.java    |   2 +-
 .../transform/DefaultMathTransformFactory.java     | 210 ++++++++++++++++-----
 .../org/apache/sis/geometry/TransformTestCase.java |   7 +-
 .../org/apache/sis/internal/util/Constants.java    |   9 +-
 16 files changed, 433 insertions(+), 72 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
index 8fab5d5c84..3e3ba90fdb 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
@@ -60,6 +60,7 @@ import org.apache.sis.referencing.datum.DefaultEllipsoid;
 import 
org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform;
 
 import static java.lang.Float.parseFloat;
+import static org.apache.sis.internal.util.Constants.DIM;
 
 
 /**
@@ -324,7 +325,7 @@ public class FranceGeocentricInterpolation extends 
GeodeticOperation {
             case 2:  break;
             case 3:  withHeights = true; break;
             default: throw new InvalidParameterValueException(Errors.format(
-                            Errors.Keys.IllegalArgumentValue_2, "dim", dim), 
"dim", dim);
+                            Errors.Keys.IllegalArgumentValue_2, DIM, dim), 
DIM, dim);
         }
         final Path file = pg.getMandatoryValue(FILE);
         final DatumShiftGridFile<Angle,Length> grid = getOrLoad(file,
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
index 32f5e451d7..2718c11d54 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffineBetweenGeographic.java
@@ -161,7 +161,7 @@ public abstract class GeocentricAffineBetweenGeographic 
extends GeocentricAffine
         SRC_SEMI_MINOR = 
builder.addName("src_semi_minor").createStrictlyPositive(Double.NaN, 
Units.METRE);
         TGT_SEMI_MAJOR = 
builder.addName("tgt_semi_major").createStrictlyPositive(Double.NaN, 
Units.METRE);
         TGT_SEMI_MINOR = 
builder.addName("tgt_semi_minor").createStrictlyPositive(Double.NaN, 
Units.METRE);
-        DIMENSION      = 
builder.addName("dim").setRequired(false).createBounded(Integer.class, 2, 3, 
null);
+        DIMENSION      = 
builder.addName(Constants.DIM).setRequired(false).createBounded(Integer.class, 
2, 3, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicToGeocentric.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicToGeocentric.java
index 0ab4bd17ea..f4a7dfa526 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicToGeocentric.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeographicToGeocentric.java
@@ -91,7 +91,7 @@ public final class GeographicToGeocentric extends 
GeodeticOperation {
     public static final ParameterDescriptorGroup PARAMETERS;
     static {
         final ParameterBuilder builder = builder();
-        DIMENSION = builder.addName(Citations.SIS, 
"dim").setRequired(false).createBounded(Integer.class, 2, 3, 3);
+        DIMENSION = builder.addName(Citations.SIS, 
Constants.DIM).setRequired(false).createBounded(Integer.class, 2, 3, 3);
         PARAMETERS = builder
                 .addIdentifier("9602")
                 .addName("Geographic/geocentric conversions")
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
index 3e6c8d232c..9c728a6578 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
@@ -209,7 +209,7 @@ public final class Molodensky extends 
GeocentricAffineBetweenGeographic {
             final int n = dim;                            // Unboxing.
             if (n != 2 && n != 3) {
                 throw new InvalidParameterValueException(Errors.format(
-                        Errors.Keys.IllegalArgumentValue_2, "dim", dim), 
"dim", dim);
+                        Errors.Keys.IllegalArgumentValue_2, Constants.DIM, 
dim), Constants.DIM, dim);
             }
             sourceDimensions = targetDimensions = n;
         }
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
index 061a620e73..a3564bedb2 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
@@ -319,7 +319,7 @@ public abstract class AbstractParameterDescriptor extends 
AbstractIdentifiedObje
 
     /**
      * Formats this descriptor as a pseudo-<cite>Well Known Text</cite> 
element. The WKT specification
-     * does not define any representation of parameter descriptors. Apache SIS 
fallback on a list of
+     * does not define any representation of parameter descriptors. Apache SIS 
fallbacks on a list of
      * {@linkplain DefaultParameterDescriptor#formatTo(Formatter) descriptors}.
      * The text formatted by this method is {@linkplain 
Formatter#setInvalidWKT flagged as invalid WKT}.
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/FilteredParameters.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/FilteredParameters.java
new file mode 100644
index 0000000000..569ff9561f
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/FilteredParameters.java
@@ -0,0 +1,114 @@
+/*
+ * 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.parameter;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.opengis.parameter.GeneralParameterDescriptor;
+import org.opengis.parameter.GeneralParameterValue;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.util.ComparisonMode;
+
+
+/**
+ * Wraps the given group of parameters, but hiding some parameters.
+ * This is used for hiding contextual parameters such as "semi_major".
+ * Hidden parameters will still be provided if explicitly requested.
+ * This filtered list is unmodifiable.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since   1.3
+ * @module
+ */
+final class FilteredParameters extends UnmodifiableParameterValueGroup {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 4880548875706032438L;
+
+    /**
+     * The filtered parameter values.
+     */
+    @SuppressWarnings("serial")         // Not statically typed as 
Serializable.
+    private final GeneralParameterValue[] filtered;
+
+    /**
+     * Creates a filtered view of given parameters.
+     */
+    private FilteredParameters(final UnmodifiableParameterValueGroup source, 
final GeneralParameterValue[] filtered) {
+        super(source);
+        this.filtered = filtered;
+    }
+
+    /**
+     * Creates a filtered view of given parameters.
+     * This method takes a snapshot of descriptor list using the given filter.
+     * No reference to that filter is kept after this method execution.
+     *
+     * @param  source  the group of parameters where values are actually 
stored.
+     * @param  filter    filter for deciding whether to keep a parameter.
+     * @return the filtered parameters. May be {@code source} itself.
+     */
+    static UnmodifiableParameterValueGroup create(final 
UnmodifiableParameterValueGroup source,
+            final Predicate<? super GeneralParameterDescriptor> filter)
+    {
+        if (source != null && filter != null) {
+            final List<GeneralParameterValue> sources = source.values();
+            final GeneralParameterValue[] filtered = new 
GeneralParameterValue[sources.size()];
+            int count = 0;
+            for (final GeneralParameterValue value : sources) {
+                if (filter.test(value.getDescriptor())) {
+                    filtered[count++] = value;
+                }
+            }
+            if (count != filtered.length) {
+                return new FilteredParameters(source, Arrays.copyOf(filtered, 
count));
+            }
+        }
+        return source;                // Nothing to filter.
+    }
+
+    /**
+     * Returns a filtered view over the parameter value.
+     */
+    @Override
+    public List<GeneralParameterValue> values() {
+        return UnmodifiableArrayList.wrap(filtered);
+    }
+
+    /**
+     * Compares the specified object with this parameter for equality.
+     */
+    @Override
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (super.equals(object, mode) && mode == ComparisonMode.STRICT) {
+            final FilteredParameters other = (FilteredParameters) object;
+            return Arrays.equals(filtered, other.filtered);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash value for this parameter.
+     */
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ filtered.length;
+    }
+}
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
index fc7ef71141..791b8ac013 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
@@ -19,6 +19,7 @@ package org.apache.sis.parameter;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.List;
+import java.util.function.Predicate;
 import java.io.Serializable;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.measure.Unit;
@@ -114,6 +115,18 @@ public abstract class Parameters implements 
ParameterValueGroup, Cloneable {
     protected Parameters() {
     }
 
+    /**
+     * Returns {@code true} if the given parameter group is a non-null 
instance created by {@code unmodifiable(…)}.
+     *
+     * @param  parameters  the parameter group to test. Can be {@code null}.
+     * @return whether the given parameters are non-null and unmodifiable.
+     *
+     * @since 1.3
+     */
+    public static boolean isUnmodifiable(final ParameterValueGroup parameters) 
{
+        return (parameters instanceof UnmodifiableParameterValueGroup);
+    }
+
     /**
      * Returns the given parameter value group as an unmodifiable {@code 
Parameters} instance.
      * If the given parameters is already an unmodifiable instance of {@code 
Parameters},
@@ -132,6 +145,34 @@ public abstract class Parameters implements 
ParameterValueGroup, Cloneable {
         return UnmodifiableParameterValueGroup.create(parameters);
     }
 
+    /**
+     * Returns the given parameter value group as an unmodifiable {@code 
Parameters} instance
+     * with some parameters hidden. The hidden parameters are excluded from 
the list returned
+     * by {@link Parameters#values()}, but are otherwise still accessible when 
the hidden
+     * parameters is explicitly named in a call to {@link #parameter(String)}.
+     *
+     * <div class="note"><b>Use case:</b>
+     * this method is used for hiding parameters that should be inferred from 
the context.
+     * For example the {@code "semi_major"} and {@code "semi_minor"} 
parameters are included
+     * in the list of {@link org.opengis.referencing.operation.MathTransform} 
parameters
+     * because that class has no way to know the values if they are not 
explicitly provided.
+     * But those semi-axis length parameters should not be included in the 
list of
+     * {@link org.opengis.referencing.operation.CoordinateOperation} parameters
+     * because they are inferred from the context (the source and target 
CRS).</div>
+     *
+     * @param  parameters  the parameters to make unmodifiable, or {@code 
null}.
+     * @param  filter      specifies which source parameters to keep visible, 
or {@code null} if no filtering.
+     * @return an unmodifiable group with the parameters of the given group to 
keep visible,
+     *         or {@code null} if the {@code parameters} argument was null.
+     *
+     * @since 1.3
+     */
+    public static Parameters unmodifiable(final ParameterValueGroup parameters,
+            final Predicate<? super GeneralParameterDescriptor> filter)
+    {
+        return 
FilteredParameters.create(UnmodifiableParameterValueGroup.create(parameters), 
filter);
+    }
+
     /**
      * Returns the given parameter value group as a {@code Parameters} 
instance.
      * If the given parameters is already an instance of {@code Parameters}, 
then it is returned as-is.
@@ -156,6 +197,8 @@ public abstract class Parameters implements 
ParameterValueGroup, Cloneable {
     @SuppressWarnings("CloneDoesntCallSuperClone")
     private static final class Wrapper extends Parameters implements 
Serializable {
         private static final long serialVersionUID = -5491790565456920471L;
+
+        @SuppressWarnings("serial")         // Not statically typed as 
Serializable.
         private final ParameterValueGroup delegate;
         Wrapper(final ParameterValueGroup delegate) {this.delegate = delegate;}
 
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValueGroup.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValueGroup.java
index f3e7b7c0f0..b5c94d2850 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValueGroup.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValueGroup.java
@@ -44,11 +44,11 @@ import org.apache.sis.internal.util.UnmodifiableArrayList;
  * the addition of {@code semi_major} and {@code semi_minor} parameters by 
{@code DefaultMathTransformFactory}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.3
  * @since   0.7
  * @module
  */
-final class UnmodifiableParameterValueGroup extends Parameters implements 
LenientComparable, Serializable {
+class UnmodifiableParameterValueGroup extends Parameters implements 
LenientComparable, Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -70,6 +70,17 @@ final class UnmodifiableParameterValueGroup extends 
Parameters implements Lenien
     @SuppressWarnings("serial")         // Not statically typed as 
Serializable.
     private final List<GeneralParameterValue> values;
 
+    /**
+     * Creates a copy of the given parameter group.
+     * This is used by {@link FilteredParameters} constructor only.
+     *
+     * @param group  the group of values to copy.
+     */
+    UnmodifiableParameterValueGroup(final UnmodifiableParameterValueGroup 
group) {
+        descriptor = group.descriptor;
+        values     = group.values;
+    }
+
     /**
      * Creates a new unmodifiable parameter group.
      *
@@ -133,7 +144,7 @@ final class UnmodifiableParameterValueGroup extends 
Parameters implements Lenien
      * {@link #parameterIfExist(String)}.
      */
     @Override
-    boolean isKnownImplementation() {
+    final boolean isKnownImplementation() {
         return true;
     }
 
@@ -141,7 +152,7 @@ final class UnmodifiableParameterValueGroup extends 
Parameters implements Lenien
      * Returns the value in this group for the specified name.
      */
     @Override
-    public ParameterValue<?> parameter(final String name) throws 
ParameterNotFoundException {
+    public final ParameterValue<?> parameter(final String name) throws 
ParameterNotFoundException {
         ArgumentChecks.ensureNonNull("name", name);
         final ParameterValue<?> value = parameterIfExist(name);
         if (value != null) {
@@ -178,7 +189,7 @@ final class UnmodifiableParameterValueGroup extends 
Parameters implements Lenien
      * Operation not allowed.
      */
     @Override
-    public ParameterValueGroup addGroup(final String name) throws 
IllegalStateException {
+    public final ParameterValueGroup addGroup(final String name) throws 
IllegalStateException {
         throw new 
UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, 
ParameterValueGroup.class));
     }
 
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 d9f95c1eda..ad0735e7f1 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
@@ -20,16 +20,19 @@ import java.util.Map;
 import java.util.List;
 import java.util.IdentityHashMap;
 import java.util.Objects;
+import java.util.function.Predicate;
 import javax.xml.bind.Unmarshaller;
 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 org.opengis.util.GenericName;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.parameter.InvalidParameterValueException;
 import org.opengis.referencing.operation.SingleOperation;
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.MathTransform;
@@ -64,7 +67,7 @@ import static org.apache.sis.util.Utilities.deepEquals;
  * {@link DefaultPassThroughOperation}.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   0.6
  * @module
  */
@@ -102,6 +105,7 @@ class AbstractSingleOperation extends 
AbstractCoordinateOperation implements Sin
      * at XML unmarshalling time by {@link 
#setParameters(GeneralParameterValue[])}.</p>
      *
      * @see #getParameterValues()
+     * @see #setParameterValues(ParameterValueGroup, Map)
      */
     @SuppressWarnings("serial")         // Not statically typed as 
Serializable.
     ParameterValueGroup parameters;
@@ -127,7 +131,7 @@ class AbstractSingleOperation extends 
AbstractCoordinateOperation implements Sin
          * However there is a few cases, for example the Molodenski transform, 
where we can not infer the
          * parameters easily because the operation is implemented by a 
concatenation of math transforms.
          */
-        parameters = Parameters.unmodifiable(Containers.property(properties, 
CoordinateOperations.PARAMETERS_KEY, ParameterValueGroup.class));
+        setParameterValues(Containers.property(properties, 
CoordinateOperations.PARAMETERS_KEY, ParameterValueGroup.class), null);
     }
 
     /**
@@ -222,6 +226,60 @@ class AbstractSingleOperation extends 
AbstractCoordinateOperation implements Sin
         return (parameters != null) ? parameters : super.getParameterValues();
     }
 
+    /**
+     * Sets the parameter values to a copy of given parameters,
+     * making sure that the parameters are compatible with the ones expected 
by the operation method.
+     * This method should be invoked by constructors only, after {@link 
#method} has been initialized.
+     *
+     * <p>If {@code ignore} is non-null, then parameters associated to {@link 
Boolean#TRUE} may be hidden.
+     * This situation happens when this operation has been initialized from a 
<em>defining conversion</em>
+     * and the caller refined the parameters using information provided by the 
math transform factory.
+     * On one hand, we want to take advantage of additional information 
present in {@code definition}
+     * such as OGC aliases (those information are often missing in {@link 
#method} if the latter
+     * is not a {@link 
org.apache.sis.referencing.operation.transform.MathTransformProvider}).
+     * But on the other hand, {@code definition} may contain contextual 
parameters (ellipsoid semi-axis lengths)
+     * which are unknown to {@link #method} and would cause an {@link 
InvalidParameterValueException} if we try
+     * to set them. We could replace {@link #method}, but if the latter was 
created from EPSG database it also
+     * contains metadata not present in {@code definition} descriptor. The 
compromise applied in this method is
+     * to keep {@link #method} as provided by user, also keep {@code 
definition} descriptor as supplied even if
+     * it is different than {@link #method} descriptor, but hide (not remove) 
parameters that are known
+     * to be redundant with information that can be inferred from the 
context.</p>
+     *
+     * @param  definition  the parameter to set, or {@code null} if none.
+     * @param  ignore      parameters to hide when the associated value is 
{@link Boolean#TRUE},
+     *                     or {@code null} for no filtering. This map may be 
modified in-place.
+     */
+    final void setParameterValues(ParameterValueGroup definition, final 
Map<String,Boolean> ignore) {
+        Predicate<GeneralParameterDescriptor> filter = null;
+        if (ignore != null) {
+            /*
+             * We enter in this block if creating a complete conversion from a 
defining conversion.
+             * The `method` instance typically come from an EPSG factory, so 
we want to keep it.
+             * The `definition` descriptor typically come from a 
`MathTransformProvider`,
+             * so we also want to keep it because it contains OGC aliases not 
present in `method`.
+             * For making both of them compatible, we will remove all 
contextual parameters
+             * (e.g. "semi_major") unless those parameters are known to 
`method`.
+             */
+            for (final GeneralParameterDescriptor accepted : 
method.getParameters().descriptors()) {
+                ignore.remove(accepted.getName().getCode());
+                for (final GenericName alias : accepted.getAlias()) {
+                    ignore.remove(alias.tip().toString());              // Do 
not ignore known parameters.
+                }
+            }
+            filter = (candidate) -> {                     // Exclude only if 
`ignore.get(…) == true`.
+                Boolean isContextual = 
ignore.get(candidate.getName().getCode());
+                if (isContextual == null) {
+                    for (final GenericName alias : candidate.getAlias()) {
+                        isContextual = ignore.get(alias.tip().toString());
+                        if (isContextual != null) break;
+                    }
+                }
+                return !Boolean.TRUE.equals(isContextual);
+            };
+        }
+        parameters = Parameters.unmodifiable(definition, filter);
+    }
+
     /**
      * Compares this coordinate operation with the specified object for 
equality. If the {@code mode} argument
      * is {@link ComparisonMode#STRICT} or {@link ComparisonMode#BY_CONTRACT 
BY_CONTRACT}, then all available
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
index f61f85df2b..597b53e7a5 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
@@ -113,7 +113,7 @@ import static 
org.apache.sis.util.Utilities.equalsIgnoreMetadata;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see 
DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem, 
CoordinateReferenceSystem, CoordinateOperationContext)
  *
@@ -641,7 +641,7 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
                 descriptor = GeocentricToGeographic.PARAMETERS;
             }
             parameters = descriptor.createValue();
-            
parameters.parameter("dim").setValue(geographic.getCoordinateSystem().getDimension());
+            
parameters.parameter(Constants.DIM).setValue(geographic.getCoordinateSystem().getDimension());
         } else {
             /*
              * Coordinate system change (including change in the number of 
dimensions) without datum shift.
@@ -681,9 +681,10 @@ public class CoordinateOperationFinder extends 
CoordinateOperationRegistry {
          */
         MathTransform transform = 
mtFactory.createParameterizedTransform(parameters, context);
         if (method == null) {
-            method = mtFactory.getLastMethodUsed();
+            method = context.getMethodUsed();
         }
         if (before != null) {
+            parameters = null;      // Providing parameters would be 
misleading because they apply to only a step of the operation.
             transform = mtFactory.createConcatenatedTransform(before, 
transform);
             if (after != null) {
                 transform = mtFactory.createConcatenatedTransform(transform, 
after);
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 52a76c2844..5e0d59b57b 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
@@ -1100,16 +1100,17 @@ class CoordinateOperationRegistry {
                     if (mtFactory instanceof DefaultMathTransformFactory) {
                         if (forward) sourceCRS = toGeodetic3D(sourceCRS, 
source3D);
                         else         targetCRS = toGeodetic3D(targetCRS, 
target3D);
+                        final DefaultMathTransformFactory.Context context;
                         final MathTransform mt;
                         try {
+                            context = 
ReferencingUtilities.createTransformContext(sourceCRS, targetCRS);
                             mt = ((DefaultMathTransformFactory) 
mtFactory).createParameterizedTransform(
-                                    ((SingleOperation) 
op).getParameterValues(),
-                                    
ReferencingUtilities.createTransformContext(sourceCRS, targetCRS));
+                                    ((SingleOperation) 
op).getParameterValues(), context);
                         } catch (InvalidGeodeticParameterException e) {
                             log(null, e);
                             break;
                         }
-                        operations.set(recreate(op, sourceCRS, targetCRS, mt, 
mtFactory.getLastMethodUsed()));
+                        operations.set(recreate(op, sourceCRS, targetCRS, mt, 
context.getMethodUsed()));
                         return true;
                     }
                 }
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 8cec2a3414..3fff4153d9 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,7 +36,6 @@ 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.parameter.Parameters;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Utilities;
@@ -78,7 +77,7 @@ import org.apache.sis.util.Utilities;
  * by many objects and passed between threads without synchronization.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 1.3
  *
  * @see DefaultTransformation
  *
@@ -212,9 +211,7 @@ public class DefaultConversion extends 
AbstractSingleOperation implements Conver
             throw new 
IllegalArgumentException(Resources.forProperties(properties)
                     .getString(Resources.Keys.UnspecifiedParameterValues));
         }
-        if (parameters != null) {
-            this.parameters = Parameters.unmodifiable(parameters);
-        }
+        setParameterValues(parameters, null);
         checkDimensions(properties);
     }
 
@@ -262,7 +259,8 @@ public class DefaultConversion extends 
AbstractSingleOperation implements Conver
                     context = 
ReferencingUtilities.createTransformContext(source, target);
                 }
                 transform = ((DefaultMathTransformFactory) 
factory).createParameterizedTransform(parameters, context);
-                parameters = 
Parameters.unmodifiable(context.getCompletedParameters());
+                actual[0] = context.getMethodUsed();
+                setParameterValues(context.getCompletedParameters(), 
context.getContextualParameters());
             } else {
                 /*
                  * Fallback for non-SIS implementation. Equivalent to the 
above code, except that we can
@@ -270,8 +268,8 @@ public class DefaultConversion extends 
AbstractSingleOperation implements Conver
                  * the code should work anyway.
                  */
                 transform = factory.createBaseToDerived(source, parameters, 
target.getCoordinateSystem());
+                actual[0] = factory.getLastMethodUsed();
             }
-            actual[0] = factory.getLastMethodUsed();
         } else {
             /*
              * If the user specified explicitly a MathTransform, we may still 
need to swap or scale axes.
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
index c993ff98b5..f54d0dd35f 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
@@ -91,7 +91,7 @@
  * for example by specifying the area of interest.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   0.6
  * @module
  */
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index 5d5925c287..f7543926c4 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -16,10 +16,11 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import java.util.IdentityHashMap;
+import java.util.Set;
 import java.util.Map;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.ServiceLoader;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReference;
@@ -27,8 +28,8 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.lang.reflect.Constructor;
 import java.io.Serializable;
-import javax.measure.quantity.Length;
 import javax.measure.Unit;
+import javax.measure.quantity.Length;
 import javax.measure.IncommensurableException;
 
 import org.opengis.parameter.ParameterValue;
@@ -37,6 +38,7 @@ import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.parameter.InvalidParameterNameException;
 import org.opengis.parameter.InvalidParameterValueException;
+import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -52,8 +54,9 @@ import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
 
 import org.apache.sis.io.wkt.Parser;
-import org.apache.sis.internal.referencing.LazySet;
+import org.apache.sis.internal.util.Strings;
 import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.referencing.LazySet;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
@@ -547,6 +550,12 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
      * or anything else related to datum. Datum changes have dedicated {@link 
OperationMethod},
      * for example <cite>"Longitude rotation"</cite> (EPSG:9601) for changing 
the prime meridian.
      *
+     * <h2>Scope</h2>
+     * Instances of this class should be short-lived
+     * (they exist only the time needed for creating a {@link MathTransform})
+     * and should not be shared (because they provide no immutability 
guarantees).
+     * This class is not thread-safe.
+     *
      * @author  Martin Desruisseaux (Geomatys)
      * @version 1.3
      * @since   0.7
@@ -557,7 +566,7 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         /**
          * For cross-version compatibility.
          */
-        private static final long serialVersionUID = 6963581151055917955L;
+        private static final long serialVersionUID = -239563539875674709L;
 
         /**
          * Coordinate system of the source or target points.
@@ -574,8 +583,6 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * The provider that created the parameterized {@link MathTransform} 
instance, or {@code null}
          * if this information does not apply. This field is used for 
transferring information between
          * {@code createParameterizedTransform(…)} and {@code 
swapAndScaleAxes(…)}.
-         *
-         * @todo We could make this information public as a replacement of 
{@link #getLastMethodUsed()}.
          */
         private OperationMethod provider;
 
@@ -586,10 +593,18 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          */
         private ParameterValueGroup parameters;
 
+        /**
+         * Names of parameters which have been inferred from context.
+         *
+         * @see #getContextualParameters()
+         */
+        private final Map<String,Boolean> contextualParameters;
+
         /**
          * Creates a new context with all properties initialized to {@code 
null}.
          */
         public Context() {
+            contextualParameters = new HashMap<>();
         }
 
         /**
@@ -799,11 +814,66 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
         }
 
         /**
-         * Returns the parameter values used for the math transform creation, 
including the parameters completed
-         * by the factory.
+         * Returns the operation method used for the math transform creation.
+         * This is the same information than {@link #getLastMethodUsed()} but 
more stable
+         * (not affected by transforms created with other contexts).
+         *
+         * @return the operation method used by the factory.
+         * @throws IllegalStateException if {@link 
#createParameterizedTransform(ParameterValueGroup, Context)}
+         *         has not yet been invoked.
+         *
+         * @see #getLastMethodUsed()
+         *
+         * @since 1.3
+         */
+        public OperationMethod getMethodUsed() {
+            if (provider != null) {
+                return provider;
+            }
+            throw new 
IllegalStateException(Resources.format(Resources.Keys.UnspecifiedParameterValues));
+        }
+
+        /**
+         * Returns the names of parameters that have been inferred from the 
context.
+         * The set of keys can contain any of {@code "dim"},
+         * {@code     "semi_major"}, {@code     "semi_minor"},
+         * {@code "src_semi_major"}, {@code "src_semi_minor"},
+         * {@code "tgt_semi_major"}, {@code "tgt_semi_minor"} and/or
+         * {@code "inverse_flattening"}, depending on the operation method 
used.
+         * The parameters named in that set are included in the parameters
+         * returned by {@link #getCompletedParameters()}.
+         *
+         * <h4>Associated boolean values</h4>
+         * The associated boolean in the map tells whether the named parameter 
value is really contextual.
+         * The boolean is {@code FALSE} if the user explicitly specified a 
value in the parameters given to
+         * the {@link #createParameterizedTransform(ParameterValueGroup, 
Context)} method,
+         * and that value is different than the value inferred from the 
context.
+         * Such inconsistencies are also logged at {@link Level#WARNING}.
+         * In all other cases
+         * (no value specified by the user, or a value was specified but is 
consistent with the context),
+         * the associated boolean in the map is {@code TRUE}.
+         *
+         * <h4>Mutability</h4>
+         * The returned map is modifiable for making easier for callers to 
amend the contextual information.
+         * This map is not used by {@code Context} except for information 
purposes (e.g. in {@link #toString()}).
+         * In particular, modifications of this map have no incidence on the 
created {@link MathTransform}.
+         *
+         * @return names of parameters inferred from context.
+         *
+         * @since 1.3
+         */
+        @SuppressWarnings("ReturnOfCollectionOrArrayField")         // 
Modifiable by method contract.
+        public Map<String,Boolean> getContextualParameters() {
+            return contextualParameters;
+        }
+
+        /**
+         * Returns the parameter values used for the math transform creation,
+         * including the parameters completed by the factory.
+         * The parameters inferred from the context are listed by {@link 
#getContextualParameters()}.
          *
          * @return the parameter values used by the factory.
-         * @throws IllegalStateException if {@link 
DefaultMathTransformFactory#createParameterizedTransform(ParameterValueGroup, 
Context)}
+         * @throws IllegalStateException if {@link 
#createParameterizedTransform(ParameterValueGroup, Context)}
          *         has not yet been invoked.
          */
         public ParameterValueGroup getCompletedParameters() {
@@ -823,31 +893,40 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
          * parameter value validity. This may result in an {@link 
InvalidParameterNameException}
          * or {@link InvalidParameterValueException} to be thrown.</p>
          *
-         * @param  writable  {@code true} if this method should also check 
that the parameters group is not an
-         *                   instance of {@code 
UnmodifiableParameterValueGroup}. Current implementation assumes
-         *                   that modifiable parameters are instances of 
{@link DefaultParameterValueGroup}.
+         * @param  writable  {@code true} if this method should also check 
that the parameters group is editable.
          * @throws IllegalArgumentException if the copy can not be performed 
because a parameter has
          *         a unrecognized name or an illegal value.
          */
         private void ensureCompatibleParameters(final boolean writable) throws 
IllegalArgumentException {
             final ParameterDescriptorGroup expected = provider.getParameters();
-            if (parameters.getDescriptor() != expected ||
-                    (writable &&  (parameters instanceof Parameters)
-                              && !(parameters instanceof 
DefaultParameterValueGroup)))
-            {
+            if (parameters.getDescriptor() != expected || (writable && 
Parameters.isUnmodifiable(parameters))) {
                 final ParameterValueGroup copy = expected.createValue();
                 Parameters.copy(parameters, copy);
                 parameters = copy;
             }
         }
 
+        /**
+         * Gets a parameter for which to infer a value from the context.
+         * The consistency flag is initially set to {@link Boolean#TRUE}.
+         *
+         * @param  name  name of the contextual parameter.
+         * @return the parameter.
+         * @throws ParameterNotFoundException if the parameter was not found.
+         */
+        private ParameterValue<?> getContextualParameter(final String name) 
throws ParameterNotFoundException {
+            ParameterValue<?> parameter = parameters.parameter(name);
+            contextualParameters.put(name, Boolean.TRUE);               // Add 
only if above line succeeded.
+            return parameter;
+        }
+
         /**
          * Returns the value of the given parameter in the given unit, or 
{@code NaN} if the parameter is not set.
          *
          * <p><b>NOTE:</b> Do not merge this function with {@code 
ensureSet(…)}. We keep those two methods
          * separated in order to give to {@code 
createParameterizedTransform(…)} a "all or nothing" behavior.</p>
          */
-        private static double getValue(final ParameterValue<?> parameter, 
final Unit<Length> unit) {
+        private static double getValue(final ParameterValue<?> parameter, 
final Unit<?> unit) {
             return (parameter.getValue() != null) ? 
parameter.doubleValue(unit) : Double.NaN;
         }
 
@@ -908,8 +987,8 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                 ParameterValue<?> mismatchedParam = null;
                 double mismatchedValue = 0;
                 try {
-                    final ParameterValue<?> ap = 
parameters.parameter(semiMajor);
-                    final ParameterValue<?> bp = 
parameters.parameter(semiMinor);
+                    final ParameterValue<?> ap = 
getContextualParameter(semiMajor);
+                    final ParameterValue<?> bp = 
getContextualParameter(semiMinor);
                     final Unit<Length> unit = ellipsoid.getAxisUnit();
                     /*
                      * The two calls to getValue(…) shall succeed before we 
write anything, in order to have a
@@ -920,10 +999,12 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                     final double b   = getValue(bp, unit);
                     final double tol = 
Units.METRE.getConverterTo(unit).convert(ELLIPSOID_PRECISION);
                     if (ensureSet(ap, a, ellipsoid.getSemiMajorAxis(), unit, 
tol)) {
+                        contextualParameters.put(semiMajor, Boolean.FALSE);
                         mismatchedParam = ap;
                         mismatchedValue = a;
                     }
                     if (ensureSet(bp, b, ellipsoid.getSemiMinorAxis(), unit, 
tol)) {
+                        contextualParameters.put(semiMinor, Boolean.FALSE);
                         mismatchedParam = bp;
                         mismatchedValue = b;
                     }
@@ -940,17 +1021,6 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                         failure.addSuppressed(e);
                     }
                 }
-                final boolean isIvfDefinitive;
-                if (mismatchedParam != null) {
-                    final LogRecord record = 
Resources.forLocale(null).getLogRecord(Level.WARNING,
-                            Resources.Keys.MismatchedEllipsoidAxisLength_3, 
ellipsoid.getName().getCode(),
-                            
mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue);
-                    record.setLoggerName(Loggers.COORDINATE_OPERATION);
-                    Logging.log(DefaultMathTransformFactory.class, 
"createParameterizedTransform", record);
-                    isIvfDefinitive = false;
-                } else {
-                    isIvfDefinitive = inverseFlattening && 
ellipsoid.isIvfDefinitive();
-                }
                 /*
                  * Following is specific to Apache SIS. We use this 
non-standard API for allowing the
                  * NormalizedProjection class (our base class for all map 
projection implementations)
@@ -958,8 +1028,14 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                  * instead of the semi-major axis length. It makes a small 
difference in the accuracy
                  * of the eccentricity parameter.
                  */
-                if (isIvfDefinitive) try {
-                    
parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
+                if (mismatchedParam == null && inverseFlattening && 
ellipsoid.isIvfDefinitive()) try {
+                    final ParameterValue<?> ep = 
getContextualParameter(Constants.INVERSE_FLATTENING);
+                    final double e = getValue(ep, Units.UNITY);
+                    if (ensureSet(ep, e, ellipsoid.getInverseFlattening(), 
Units.UNITY, 1E-10)) {
+                        contextualParameters.put(Constants.INVERSE_FLATTENING, 
Boolean.FALSE);
+                        mismatchedParam = ep;
+                        mismatchedValue = e;
+                    }
                 } catch (ParameterNotFoundException e) {
                     /*
                      * Should never happen with Apache SIS implementation, but 
may happen if the given parameters come
@@ -969,6 +1045,18 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                     
Logging.recoverableException(getLogger(Loggers.COORDINATE_OPERATION),
                             DefaultMathTransformFactory.class, 
"createParameterizedTransform", e);
                 }
+                /*
+                 * If a parameter was explicitly specified by user but has a 
value inconsistent with the context,
+                 * log a warning. In addition, the associated boolean value in 
`contextualParameters` map should
+                 * have been set to `Boolean.FALSE`.
+                 */
+                if (mismatchedParam != null) {
+                    final LogRecord record = 
Resources.forLocale(null).getLogRecord(Level.WARNING,
+                            Resources.Keys.MismatchedEllipsoidAxisLength_3, 
ellipsoid.getName().getCode(),
+                            
mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue);
+                    record.setLoggerName(Loggers.COORDINATE_OPERATION);
+                    Logging.log(DefaultMathTransformFactory.class, 
"createParameterizedTransform", record);
+                }
             }
             return failure;
         }
@@ -1054,7 +1142,7 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             RuntimeException failure = null;
             if (sourceCS != null) try {
                 ensureCompatibleParameters(true);
-                final ParameterValue<?> p = parameters.parameter("dim");    // 
Really `parameters`, not `userParams`.
+                final ParameterValue<?> p = 
getContextualParameter(Constants.DIM);
                 if (p.getValue() == null) {
                     p.setValue(sourceCS.getDimension());
                 }
@@ -1065,6 +1153,48 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             failure = setEllipsoid(getTargetEllipsoid(), "tgt_semi_major", 
"tgt_semi_minor", false, failure);
             return failure;
         }
+
+        /**
+         * Returns a string representation of this context for debugging 
purposes.
+         * Current implementation write the name of source/target coordinate 
systems and ellipsoids.
+         * If {@linkplain #getContextualParameters() contextual parameters} 
have already been inferred,
+         * then their names are appended with inconsistent parameters (if any) 
written on a separated line.
+         *
+         * @return a string representation of this context.
+         */
+        @Override
+        public String toString() {
+            final Object[] properties = {
+                "sourceCS", sourceCS, "sourceEllipsoid", sourceEllipsoid,
+                "targetCS", targetCS, "targetEllipsoid", targetEllipsoid
+            };
+            for (int i=1; i<properties.length; i += 2) {
+                final IdentifiedObject value = (IdentifiedObject) 
properties[i];
+                if (value != null) properties[i] = value.getName();
+            }
+            String text = Strings.toString(getClass(), properties);
+            if (!contextualParameters.isEmpty()) {
+                final StringBuilder b = new StringBuilder(text);
+                boolean isContextual = true;
+                do {
+                    boolean first = true;
+                    for (final Map.Entry<String,Boolean> entry : 
contextualParameters.entrySet()) {
+                        if (entry.getValue() == isContextual) {
+                            if (first) {
+                                first = false;
+                                b.append(System.lineSeparator())
+                                 .append(isContextual ? "Contextual 
parameters" : "Inconsistencies").append(": ");
+                            } else {
+                                b.append(", ");
+                            }
+                            b.append(entry.getKey());
+                        }
+                    }
+                } while ((isContextual = !isContextual) == false);
+                text = b.toString();
+            }
+            return text;
+        }
     }
 
     /**
@@ -1190,13 +1320,6 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
             throw e;
         } finally {
             lastMethod.set(method);     // May be null in case of failure, 
which is intended.
-            if (context != null) {
-                context.provider = null;
-                /*
-                 * For now we conservatively reset the provider information to 
null. But if we choose to
-                 * make that information public in a future SIS version, then 
we would remove this code.
-                 */
-            }
         }
         return transform;
     }
@@ -1463,7 +1586,9 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
                         context.targetEllipsoid = ellipsoid;
                     }
                     final ParameterValueGroup pg = 
getDefaultParameters(operation);
-                    if (cs.getDimension() < 3) 
pg.parameter("dim").setValue(2);       // Apache SIS specific parameter.
+                    if (cs.getDimension() < 3) {
+                        pg.parameter(Constants.DIM).setValue(2);        // 
Apache SIS specific parameter.
+                    }
                     return createParameterizedTransform(pg, context);
                 }
             }
@@ -1647,6 +1772,7 @@ public class DefaultMathTransformFactory extends 
AbstractFactory implements Math
      * @return the last method used by a {@code create(…)} constructor, or 
{@code null} if unknown of unsupported.
      *
      * @see #createParameterizedTransform(ParameterValueGroup, Context)
+     * @see Context#getMethodUsed()
      */
     @Override
     public OperationMethod getLastMethodUsed() {
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
index 27233c28dd..30d9b7d88c 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
@@ -48,7 +48,7 @@ import static org.apache.sis.test.ReferencingAssert.*;
  * All tests performed by this class are two-dimensional.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @param <G>  the type of geometric objects, either {@link GeneralEnvelope} 
or {@link java.awt.geom.Rectangle2D}.
  *
@@ -271,8 +271,9 @@ public abstract strictfp class TransformTestCase<G> extends 
TestCase {
      * @see 
org.apache.sis.referencing.operation.CoordinateOperationRegistry#inverse(SingleOperation)
      */
     private static Conversion inverse(final Conversion conversion) throws 
NoninvertibleTransformException {
-        return new 
DefaultConversion(IdentifiedObjects.getProperties(conversion), 
conversion.getTargetCRS(),
-                conversion.getSourceCRS(), null, conversion.getMethod(), 
conversion.getMathTransform().inverse());
+        return new 
DefaultConversion(IdentifiedObjects.getProperties(conversion, 
Conversion.IDENTIFIERS_KEY),
+                conversion.getTargetCRS(), conversion.getSourceCRS(), null,
+                conversion.getMethod(), 
conversion.getMathTransform().inverse());
     }
 
     /**
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
index 1d9de64c96..0ae272c746 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
@@ -31,7 +31,7 @@ import org.apache.sis.util.Static;
  * creates itself the instance to be tested.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.5
  * @module
  */
@@ -126,6 +126,13 @@ public final class Constants extends Static {
      */
     public static final byte CRS1 = 1;
 
+    /**
+     * Name of a SIS-specific parameter for setting the number of dimensions 
of a coordinate operation.
+     * This constant should be used only in the context of coordinate 
operations, not in other contexts
+     * (e.g. not for the netCDF attribute of the same name).
+     */
+    public static final String DIM = "dim";
+
     /**
      * The netCDF parameter name for the Earth radius.
      */

Reply via email to