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 eef4dfff3178e743e6baf2174d75ea72327fb77b Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Oct 28 17:48:13 2022 +0200 Bug fixes related to unmarshalling of GML documents. Those bugs were identified by OGC TestBed 18 D025 scenario. --- .../sis/internal/jaxb/IdentifierMapAdapter.java | 30 ++++++++- .../org/apache/sis/internal/jaxb/package-info.java | 2 +- .../java/org/apache/sis/xml/NilObjectHandler.java | 2 +- .../java/org/apache/sis/xml/ReferenceResolver.java | 8 +-- .../jaxb/referencing/CC_OperationParameter.java | 54 +++++++++++----- .../internal/jaxb/referencing/package-info.java | 2 +- .../sis/internal/referencing/AxisDirections.java | 54 +++++++++++++--- .../sis/parameter/DefaultParameterDescriptor.java | 74 ++++++++++++++++++---- .../sis/parameter/DefaultParameterValue.java | 14 ++-- .../sis/parameter/DefaultParameterValueGroup.java | 8 +-- .../org/apache/sis/parameter/ParameterFormat.java | 3 +- .../java/org/apache/sis/parameter/Parameters.java | 9 ++- .../sis/parameter/UnmodifiableParameterValue.java | 13 ++-- .../sis/referencing/cs/CoordinateSystems.java | 7 ++ .../org/apache/sis/referencing/cs/Normalizer.java | 26 ++++---- .../operation/AbstractSingleOperation.java | 6 ++ .../operation/DefaultConcatenatedOperation.java | 2 +- .../operation/DefaultOperationMethod.java | 26 +++++--- .../storage/csv/MovingFeatureIterator.java | 3 +- .../org/apache/sis/internal/storage/csv/Store.java | 2 +- 20 files changed, 260 insertions(+), 85 deletions(-) diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java index 95dcf08a4c..2dc9ae3436 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java @@ -18,8 +18,10 @@ package org.apache.sis.internal.jaxb; import java.net.URI; import java.util.Set; +import java.util.List; import java.util.HashMap; import java.util.HashSet; +import java.util.ArrayList; import java.util.Iterator; import java.util.Collection; import java.util.Collections; @@ -81,7 +83,7 @@ import static org.apache.sis.util.collection.Containers.hashMapCapacity; * This class is thread safe if the underlying identifier collection is thread safe. * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.3 * * @see org.apache.sis.xml.IdentifiedObject * @@ -101,7 +103,10 @@ public class IdentifierMapAdapter extends AbstractMap<Citation,String> implement /** * The identifiers to wrap in a map view. + * + * @see #getIdentifiers(Class) */ + @SuppressWarnings("serial") // Not statically typed as Serializable. public final Collection<Identifier> identifiers; /** @@ -113,6 +118,29 @@ public class IdentifierMapAdapter extends AbstractMap<Citation,String> implement this.identifiers = identifiers; } + /** + * Returns the identifiers as a collection of the specified type. + * The given type is the return type of the {@code getIdentifiers()} method which is delegating to this method. + * The returned collection is modifiable only if {@link #identifiers} is already of the desired type. + * This is the case for {@link org.apache.sis.metadata.iso.ISOMetadata#getIdentifiers()}, + * which is the API for which we want modifiable collections. + * + * @param type {@code Collection.class}, {@code List.class} or {@code Set.class}. + * @return the identifiers as a collection of the specified type. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public final Collection<Identifier> getIdentifiers(final Class<?> type) { + if (!type.isInstance(identifiers)) { + if (type.isAssignableFrom(Set.class)) { + return new HashSet<>(identifiers); // TODO: use Set.copyOf in JDK10. + } + if (type.isAssignableFrom(List.class)) { + return new ArrayList<>(identifiers); // TODO: use List.copyOf in JDK10. + } + } + return identifiers; + } + /** * If the given authority is a special case, returns its {@link NonMarshalledAuthority} integer enum. * Otherwise returns -1. See javadoc for more information about special cases. diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/package-info.java index 24080ec8e7..ba864f8ea2 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/package-info.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/package-info.java @@ -35,7 +35,7 @@ * @author Cédric Briançon (Geomatys) * @author Cullen Rombach (Image Matters) * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.3 * @since 0.3 * @module */ diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/NilObjectHandler.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/NilObjectHandler.java index bf4ce28745..4cb46aeeb6 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/xml/NilObjectHandler.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/NilObjectHandler.java @@ -134,7 +134,7 @@ final class NilObjectHandler implements InvocationHandler { } case "getIdentifiers": { return (attribute instanceof IdentifierMapAdapter) ? - ((IdentifierMapAdapter) attribute).identifiers : null; + ((IdentifierMapAdapter) attribute).getIdentifiers(method.getReturnType()) : null; } case "toString": { return Strings.bracket(getInterface(proxy), attribute); diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/ReferenceResolver.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/ReferenceResolver.java index 11b8ee14f8..cca68226f5 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/xml/ReferenceResolver.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/ReferenceResolver.java @@ -68,8 +68,8 @@ public class ReferenceResolver { * <li>{@link IdentifiedObject#getIdentifierMap()} will return a {@link java.util.Map} * view over the given identifiers.</li> * <li>All other methods except the ones inherited from the {@link Object} class will return - * an empty collection, an empty array, {@code null}, {@link Double#NaN NaN}, 0 or - * {@code false}, depending on the method return type.</li> + * an empty collection, an empty array, {@code null}, {@link Double#NaN}, 0 or {@code false}, + * depending on the method return type.</li> * </ul> * * @param <T> the compile-time type of the {@code type} argument. @@ -135,10 +135,10 @@ public class ReferenceResolver { return type.cast(object); } else { final short key; - final Object args; + final Object[] args; if (object == null) { key = Errors.Keys.NotABackwardReference_1; - args = id; + args = new Object[] {id}; } else { key = Errors.Keys.UnexpectedTypeForReference_3; args = new Object[] {id, type, object.getClass()}; diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameter.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameter.java index a00d2a31f0..7970ef1f42 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameter.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameter.java @@ -37,7 +37,7 @@ import org.apache.sis.parameter.DefaultParameterDescriptor; * infer it from the actual value.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.3 * @since 0.6 * @module */ @@ -118,6 +118,40 @@ public final class CC_OperationParameter extends PropertyType<CC_OperationParame metadata = parameter; } + /** + * Returns the base class of parameter values, or {@code null} if unknown. + * This method assumes that the parameter value does not yet have a descriptor + * (which happens at GML unmarshalling time) and that the type must be derived + * from the actual value. + * + * @param param the parameter from which to get the value class. + * @return base class of values, or {@code null} if unknown. + */ + public static Class<?> valueClass(final ParameterValue<?> param) { + final Object value = param.getValue(); + return (value != null) ? value.getClass() : null; + } + + /** + * Saves the unit of measurement in a boundless range. This method should be invoked only when + * {@link #valueClass} is {@link Double} or {@code double[]}. It is the case in a well-formed GML. + * + * @param param the parameter from which to get the unit of measurement. + * @return unit of measurement wrapped in a boundless range, or {@code null} if none. + */ + public static MeasurementRange<?> valueDomain(final ParameterValue<?> param) { + Unit<?> unit = param.getUnit(); + if (unit == null) { + return null; + } + unit = unit.getSystemUnit(); + if (Units.RADIAN.equals(unit)) { + unit = Units.DEGREE; + } + return MeasurementRange.create(Double.NEGATIVE_INFINITY, false, + Double.POSITIVE_INFINITY, false, unit); + } + /** * Invoked by JAXB during unmarshalling of the enclosing {@code <gml:OperationParameter>}, * before the child {@link DefaultParameterDescriptor}. This method stores the class and @@ -129,21 +163,9 @@ public final class CC_OperationParameter extends PropertyType<CC_OperationParame */ private void beforeUnmarshal(final Unmarshaller unmarshaller, final Object parent) { if (parent instanceof ParameterValue<?>) { - final Object value = ((ParameterValue<?>) parent).getValue(); - if (value != null) { - valueClass = value.getClass(); - Unit<?> unit = ((ParameterValue<?>) parent).getUnit(); - if (unit != null) { - unit = unit.getSystemUnit(); - if (Units.RADIAN.equals(unit)) { - unit = Units.DEGREE; - } - assert (valueClass == Double.class) || (valueClass == double[].class) : valueClass; - valueDomain = MeasurementRange.create(Double.NEGATIVE_INFINITY, false, - Double.POSITIVE_INFINITY, false, unit); - } - Context.setWrapper(Context.current(), this); - } + valueClass = valueClass ((ParameterValue<?>) parent); + valueDomain = valueDomain((ParameterValue<?>) parent); + Context.setWrapper(Context.current(), this); } } diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/package-info.java index 385610001b..d081072712 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/package-info.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/package-info.java @@ -24,7 +24,7 @@ * @author Guilhem Legal (Geomatys) * @author Cédric Briançon (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.3 * * @see javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter * diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java index 32d5e1cab3..05326fc7c9 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java @@ -44,7 +44,7 @@ import static org.apache.sis.util.CharSequences.*; * Utilities methods related to {@link AxisDirection}. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.3 * @since 0.4 * @module */ @@ -76,17 +76,28 @@ public final class AxisDirections extends Static { private static final int LAST_ORDINAL = DISPLAY_DOWN.ordinal(); /** - * Distance from the origin in a polar coordinate system. - * Specified in ISO 19162 but not yet in ISO 19111. + * Forward direction. + * For an observer at the centre of the object this is will be towards its front, bow or nose. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). * - * @since 0.7 + * @since 1.3 */ - @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=UNSPECIFIED) - public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM"); + @UML(identifier="forward", obligation=CONDITIONAL, specification=UNSPECIFIED) + public static final AxisDirection FORWARD = AxisDirection.valueOf("FORWARD"); + + /** + * Starboard direction. + * For an observer at the centre of the object this will be towards its right. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + * + * @since 1.3 + */ + @UML(identifier="starboard", obligation=CONDITIONAL, specification=UNSPECIFIED) + public static final AxisDirection STARBOARD = AxisDirection.valueOf("STARBOARD"); /** * Direction of geographic angles (bearing). - * Specified in ISO 19162 but not yet in ISO 19111. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). * * @since 0.7 */ @@ -95,13 +106,22 @@ public final class AxisDirections extends Static { /** * Direction of arithmetic angles. Used in polar coordinate systems. - * Specified in ISO 19162 but not yet in ISO 19111. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). * * @since 0.7 */ @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=UNSPECIFIED) public static final AxisDirection COUNTER_CLOCKWISE = AxisDirection.valueOf("COUNTER_CLOCKWISE"); + /** + * Distance from the origin in a polar coordinate system. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + * + * @since 0.7 + */ + @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=UNSPECIFIED) + public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM"); + /** * For each direction, the opposite direction. * This map shall be immutable after construction. @@ -345,6 +365,20 @@ public final class AxisDirections extends Static { return ordinal >= COLUMN_POSITIVE.ordinal() && ordinal <= ROW_NEGATIVE.ordinal(); } + /** + * Arithmetic angle between forward/aft/port/starboard directions only. + * This is the angle as viewed from above the vehicle. + * + * @param source the start direction. + * @param target the final direction. + * @return the angle as a multiple of 90°, or {@link Integer#MIN_VALUE} if none. + */ + public static int angleForVehicle(final AxisDirection source, final AxisDirection target) { + if (source == STARBOARD && target == FORWARD) return +1; + if (source == FORWARD && target == STARBOARD) return -1; + return Integer.MIN_VALUE; + } + /** * Angle between geocentric directions only. * @@ -367,7 +401,7 @@ public final class AxisDirections extends Static { } /** - * Angle between compass directions only (not for angle between direction along meridians). + * Arithmetic angle between compass directions only (not for angle between direction along meridians). * * @param source the start direction. * @param target the final direction. @@ -394,7 +428,7 @@ public final class AxisDirections extends Static { } /** - * Angle between display directions only. + * Arithmetic angle between display directions only. * * @param source the start direction. * @param target the final direction. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java index 67a62c0c3d..9fadb81fe2 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java @@ -65,7 +65,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureCanCast; * * @author Martin Desruisseaux (IRD, Geomatys) * @author Johann Sorel (Geomatys) - * @version 0.8 + * @version 1.3 * * @param <T> the type of elements to be returned by {@link DefaultParameterValue#getValue()}. * @@ -85,10 +85,12 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i /** * The class that describe the type of parameter values. + * This field should be considered final after construction. + * This is declared non-final only for GML unmarshalling. * * @see #getValueClass() */ - private final Class<T> valueClass; + private Class<T> valueClass; /** * A set of valid values (usually from a {@linkplain CodeList code list}) @@ -110,9 +112,12 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i * <code>valueClass.{@linkplain Class#getComponentType() getComponentType()}</code>.</li> * </ul> * + * This field should be considered final after construction. + * This is declared non-final only for GML unmarshalling. + * * @see #getValueDomain() */ - private final Range<?> valueDomain; + private Range<?> valueDomain; /** * The default value for the parameter, or {@code null}. @@ -270,7 +275,6 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i * * @see #castOrCopy(ParameterDescriptor) */ - @SuppressWarnings("unchecked") protected DefaultParameterDescriptor(final ParameterDescriptor<T> descriptor) { super(descriptor); valueClass = descriptor.getValueClass(); @@ -374,6 +378,7 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i @Override @SuppressWarnings("unchecked") public Comparable<T> getMinimumValue() { + final Range<?> valueDomain = this.valueDomain; return (valueDomain != null && valueDomain.getElementType() == valueClass) ? (Comparable<T>) valueDomain.getMinValue() : null; } @@ -392,6 +397,7 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i @Override @SuppressWarnings("unchecked") public Comparable<T> getMaximumValue() { + final Range<?> valueDomain = this.valueDomain; return (valueDomain != null && valueDomain.getElementType() == valueClass) ? (Comparable<T>) valueDomain.getMaxValue() : null; } @@ -422,6 +428,7 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i */ @Override public Unit<?> getUnit() { + final Range<?> valueDomain = this.valueDomain; return (valueDomain instanceof MeasurementRange<?>) ? ((MeasurementRange<?>) valueDomain).unit() : null; } @@ -491,10 +498,10 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i } case STRICT: { final DefaultParameterDescriptor<?> that = (DefaultParameterDescriptor<?>) object; - return this.valueClass == that.valueClass && - Objects. equals(this.validValues, that.validValues) && - Objects. equals(this.valueDomain, that.valueDomain) && - Objects.deepEquals(this.defaultValue, that.defaultValue); + return valueClass == that.valueClass && + Objects. equals(validValues, that.validValues) && + Objects. equals(valueDomain, that.valueDomain) && + Objects.deepEquals(defaultValue, that.defaultValue); } } } @@ -527,7 +534,7 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i /** - * Constructs a new object in which every attributes are set to a null value. + * Constructs a new object in which attributes may be 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. * @@ -544,16 +551,57 @@ public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor i * This unsafe cast would be forbidden if this constructor was public or used in any context where the * user can choose the value of <T>. But this constructor should be invoked only during unmarshalling, * after the creation of the ParameterValue (this is the reverse creation order than what we normally - * do through the public API). The 'valueClass' should be compatible with DefaultParameterValue.value, + * do through the public API). The `valueClass` should be compatible with DefaultParameterValue.value, * and the parameterized type visible to the user should be only <?>. */ valueClass = (Class) param.valueClass; valueDomain = param.valueDomain; - } else { - valueClass = null; - valueDomain = null; } validValues = null; defaultValue = null; } + + /** + * Invoked by {@link DefaultParameterValue} when the descriptor is set after the value at unmarshalling time. + * There is two scenarios in a valid GML document. The first scenario is when the descriptor is defined inside + * the parameter value element, like below. In such case, {@link #valueClass} is defined at construction time + * by {@link #DefaultParameterDescriptor()} because the value is before the descriptor. + * + * {@preformat xml + * <gml:ParameterValue> + * <gml:value uom="…">…</gml:value> + * <gml:operationParameter> + * <gml:OperationParameter> + * … + * </gml:OperationParameter> + * </gml:operationParameter> + * </gml:ParameterValue> + * } + * + * In the second scenario shows below, the descriptor was defined before the value and is referenced by a link. + * In that case, {@link #valueClass} is {@code null} the first time that this method is invoked. It may become + * non-null if the same parameter descriptor is reused for many parameter values. + * + * {@preformat xml + * <gml:ParameterValue> + * <gml:value uom="…">…</gml:value> + * <gml:operationParameter xlink:href="#LongitudeRotation"/> + * </gml:ParameterValue> + * } + * + * This method modifies the state of this class despite the fact that it should be immutable. + * It is okay because we are updating an instance created during GML unmarshalling, and that + * instance should not have been given to user yet. + * + * @param param the parameter value from which to infer the value type. + * @return the parameter descriptor to assign to the given parameter value. + */ + @SuppressWarnings("unchecked") + final DefaultParameterDescriptor<T> setValueClass(final DefaultParameterValue<?> param) { + valueClass = (Class) Classes.findCommonClass(valueClass, CC_OperationParameter.valueClass(param)); + if (valueDomain == null) { + valueDomain = CC_OperationParameter.valueDomain(param); + } + return this; + } } diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java index 88e41ac63b..7defaf722e 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java @@ -118,7 +118,7 @@ import static org.apache.sis.util.Utilities.deepEquals; * for modifying the behavior of all getter and setter methods. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.2 + * @version 1.3 * * @param <T> the type of the value stored in this parameter. * @@ -1129,14 +1129,18 @@ convert: if (componentType != null) { /** * Invoked by JAXB at unmarshalling time. - * May also be invoked by {@link DefaultParameterValueGroup} if the descriptor as been completed + * May also be invoked by {@link DefaultParameterValueGroup} if the descriptor has been completed * with additional information provided in the {@code <gml:group>} element of a descriptor group. * * @see #getDescriptor() */ - final void setDescriptor(final ParameterDescriptor<T> descriptor) { - this.descriptor = descriptor; - assert (value == null) || descriptor.getValueClass().isInstance(value) : this; + final void setDescriptor(final ParameterDescriptor<T> p) { + descriptor = DefaultParameterDescriptor.castOrCopy(p).setValueClass(this); + /* + * A previous version was doing `assert descriptor.getValueClass().isInstance(value)` + * where the value class was inferred by `DefaultParameterDescriptor()`. But it does + * not always work, and the `NullPointerException` seems to be caught by JAXB. + */ } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java index fe091065c6..cf7de09327 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValueGroup.java @@ -559,7 +559,7 @@ scan: for (final GeneralParameterValue param : actual.values()) { /** * Invoked by JAXB for setting the group parameter descriptor. Those parameter are redundant with * the parameters associated to the values given to {@link #setValues(GeneralParameterValue[])}, - * except the the group identification (name, <i>etc.</i>) and for any optional parameters which + * except for the group identification (name, <i>etc.</i>) and for any optional parameters which * were not present in the above {@code GeneralParameterValue} array. * * @see #getDescriptor() @@ -611,9 +611,9 @@ scan: for (final GeneralParameterValue param : actual.values()) { * Appends all parameter values. In this process, we may need to update the descriptor of some values * if those descriptors changed as a result of the above merge process. * - * @param parameters The parameters to add, or {@code null} for {@link #values}. - * @param replacements The replacements to apply in the {@code GeneralParameterValue} instances. - * @param addTo Where to store the new values. + * @param parameters the parameters to add, or {@code null} for {@link #values}. + * @param replacements the replacements to apply in the {@code GeneralParameterValue} instances. + * @param addTo where to store the new values. */ @SuppressWarnings({"unchecked", "AssignmentToCollectionOrArrayFieldFromParameter"}) private void setValues(GeneralParameterValue[] parameters, diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java index a2912b2ef4..3f9e127424 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java @@ -693,7 +693,8 @@ public class ParameterFormat extends TabularFormat<Object> { /* * Writes the values, each on its own line, together with their unit of measurement. */ - final byte alignment = Number.class.isAssignableFrom(valueClass) ? TableAppender.ALIGN_RIGHT : TableAppender.ALIGN_LEFT; + final byte alignment = (valueClass != null && Number.class.isAssignableFrom(valueClass)) + ? TableAppender.ALIGN_RIGHT : TableAppender.ALIGN_LEFT; table.setCellAlignment(alignment); final int length = row.values.size(); for (int i=0; i<length; i++) { 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 e31bec5252..ccfade6322 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 @@ -38,6 +38,7 @@ import org.apache.sis.util.UnconvertibleObjectException; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ObjectConverters; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.Classes; import org.apache.sis.util.Debug; @@ -355,9 +356,15 @@ public abstract class Parameters implements ParameterValueGroup, Cloneable { if (descriptor instanceof DefaultParameterDescriptor<?>) { return ((DefaultParameterDescriptor<?>) descriptor).getValueDomain(); } - final Class<?> valueClass = descriptor.getValueClass(); + Class<?> valueClass = descriptor.getValueClass(); final Comparable<?> minimumValue = descriptor.getMinimumValue(); final Comparable<?> maximumValue = descriptor.getMaximumValue(); + if (valueClass == null) { // Should never be null, but invalid objects exist. + valueClass = Classes.findCommonClass(Classes.getClass(minimumValue), Classes.getClass(maximumValue)); + if (valueClass == null) { + valueClass = Object.class; + } + } if ((minimumValue == null || valueClass.isInstance(minimumValue)) && (maximumValue == null || valueClass.isInstance(maximumValue))) { diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java index a5ef86b428..0858703c2b 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java @@ -49,7 +49,7 @@ import org.apache.sis.util.resources.Errors; * </div> * * @author Martin Desruisseaux (Geomatys) - * @version 0.6 + * @version 1.3 * * @param <T> the type of the value stored in this parameter. * @@ -100,10 +100,13 @@ final class UnmodifiableParameterValue<T> extends DefaultParameterValue<T> { @Override public T getValue() { T value = super.getValue(); - if (value instanceof Cloneable) try { - value = getDescriptor().getValueClass().cast(Cloner.cloneIfPublic(value)); - } catch (CloneNotSupportedException e) { - throw new UnsupportedOperationException(Errors.format(Errors.Keys.CloneNotSupported_1, value.getClass()), e); + if (value instanceof Cloneable) { + final Class<T> type = getDescriptor().getValueClass(); // May be null after GML unmarshalling. + if (type != null) try { + value = type.cast(Cloner.cloneIfPublic(value)); + } catch (CloneNotSupportedException e) { + throw new UnsupportedOperationException(Errors.format(Errors.Keys.CloneNotSupported_1, value.getClass()), e); + } } return value; } diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java index 1102f49e87..ea0b5bfe80 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java @@ -239,6 +239,13 @@ public final class CoordinateSystems extends Static { if (c != Integer.MIN_VALUE) { return new Angle(c * 90); } + /* + * Check for FORWARD, AFT, PORT, STARBOARD. + */ + c = AxisDirections.angleForVehicle(source, target); + if (c != Integer.MIN_VALUE) { + return new Angle(c * 90); + } /* * Check for DISPLAY_UP, DISPLAY_DOWN, etc. assuming a flat screen. * Note that we do not check for grid directions (COLUMN_POSITIVE, diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java index f1c38a8d8b..96ad930a6f 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java @@ -106,7 +106,7 @@ final class Normalizer implements Comparable<Normalizer> { * * @see #order(AxisDirection) */ - private static final int SHIFT = 2; + private static final int SHIFT = 3; /** * Custom code list values to handle as if the where defined between two GeoAPI values. @@ -115,12 +115,15 @@ final class Normalizer implements Comparable<Normalizer> { */ private static final Map<AxisDirection,Integer> ORDER = new HashMap<>(); static { - final Map<AxisDirection,Integer> m = ORDER; // Get ordinal of last compass direction defined by GeoAPI. We will continue on the horizontal plane. - final int horizontal = (AxisDirection.NORTH.ordinal() + (AxisDirections.COMPASS_COUNT - 1)) << SHIFT; - m.put(AxisDirections.AWAY_FROM, horizontal + 1); - m.put(AxisDirections.COUNTER_CLOCKWISE, horizontal + 2); - m.put(AxisDirections.CLOCKWISE, horizontal + 3); + int code = (AxisDirection.NORTH.ordinal() + (AxisDirections.COMPASS_COUNT - 1)) << SHIFT; + for (final AxisDirection d : new AxisDirection[] { + AxisDirections.FORWARD, + AxisDirections.STARBOARD, + AxisDirections.COUNTER_CLOCKWISE, + AxisDirections.CLOCKWISE, + AxisDirections.AWAY_FROM + }) ORDER.put(d, ++code); } /** @@ -170,8 +173,9 @@ final class Normalizer implements Comparable<Normalizer> { if (d == 0) { final AxisDirection d1 = this.axis.getDirection(); final AxisDirection d2 = that.axis.getDirection(); - d = AxisDirections.angleForCompass(d2, d1); - if (d == Integer.MIN_VALUE) { + if ((d = AxisDirections.angleForCompass(d2, d1)) == Integer.MIN_VALUE && + (d = AxisDirections.angleForVehicle(d2, d1)) == Integer.MIN_VALUE) + { if (meridian != null) { if (that.meridian != null) { d = meridian.compareTo(that.meridian); @@ -445,10 +449,10 @@ final class Normalizer implements Comparable<Normalizer> { */ static AbstractCS forConvention(final CoordinateSystem cs, final AxesConvention convention) { switch (convention) { - case NORMALIZED: // Fall through + case NORMALIZED: // Fall through case DISPLAY_ORIENTED: return normalize(cs, convention, true); - case RIGHT_HANDED: return normalize(cs, null, true); - case POSITIVE_RANGE: return shiftAxisRange(cs); + case RIGHT_HANDED: return normalize(cs, null, true); + case POSITIVE_RANGE: return shiftAxisRange(cs); default: throw new AssertionError(convention); } } 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 7be7a93f96..8d89447e1c 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 @@ -463,6 +463,12 @@ class AbstractSingleOperation extends AbstractCoordinateOperation implements Sin @Override final void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { super.afterUnmarshal(unmarshaller, parent); + if (parameters == null && method != null) { + final ParameterDescriptorGroup descriptor = method.getParameters(); + if (descriptor != null && descriptor.descriptors().isEmpty()) { + parameters = descriptor.createValue(); + } + } final CoordinateReferenceSystem sourceCRS = super.getSourceCRS(); final CoordinateReferenceSystem targetCRS = super.getTargetCRS(); if (transform == null && sourceCRS != null && targetCRS != null && parameters != null) try { diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java index afa8ae1da8..c6ae10396a 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java @@ -243,7 +243,7 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp } else if (!step.isIdentity()) { flattened.add(op); } - if (mtFactory != null && step != null) { + if (mtFactory != null) { transform = (transform != null) ? mtFactory.createConcatenatedTransform(transform, step) : step; } /* diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java index 1232c0aad6..1660e4f234 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Collections; +import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @@ -117,7 +118,7 @@ import static org.apache.sis.util.ArgumentChecks.*; * {@link org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory}. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.1 + * @version 1.3 * * @see DefaultConversion * @see DefaultTransformation @@ -192,7 +193,8 @@ public class DefaultOperationMethod extends AbstractIdentifiedObject implements * The set of parameters, or {@code null} if none. * * <p><b>Consider this field as final!</b> - * This field is modified only at unmarshalling time by {@link #setDescriptors(GeneralParameterDescriptor[])}</p> + * This field is modified only at unmarshalling time by {@link #setDescriptors(GeneralParameterDescriptor[])} + * or {@link #afterUnmarshal(Unmarshaller, Object)}.</p> */ @SuppressWarnings("serial") // Not statically typed as Serializable. private ParameterDescriptorGroup parameters; @@ -303,10 +305,7 @@ public class DefaultOperationMethod extends AbstractIdentifiedObject implements targetDimensions = transform.getTargetDimensions(); if (transform instanceof Parameterized) { parameters = ((Parameterized) transform).getParameterDescriptors(); - } else { - parameters = null; } - formula = null; } /** @@ -632,9 +631,8 @@ public class DefaultOperationMethod extends AbstractIdentifiedObject implements * Returns the set of parameters. * * <div class="note"><b>Departure from the ISO 19111 standard:</b> - * this property is mandatory according ISO 19111, but may be null in Apache SIS if the - * {@link #DefaultOperationMethod(MathTransform)} constructor has been unable to infer it - * or if this {@code OperationMethod} has been read from an incomplete GML document.</div> + * this property is mandatory according ISO 19111, but may be {@code null} in Apache SIS if the + * {@link #DefaultOperationMethod(MathTransform)} constructor has been unable to infer it.</div> * * @return the parameters, or {@code null} if unknown. * @@ -971,4 +969,16 @@ public class DefaultOperationMethod extends AbstractIdentifiedObject implements parameters = new DefaultParameterDescriptorGroup(IdentifiedObjects.getProperties(previous), previous.getMinimumOccurs(), previous.getMaximumOccurs(), descriptors); } + + /** + * Invoked by JAXB after unmarshalling. If the {@code <gml:OperationMethod>} element does not contain + * any {@code <gml:parameter>}, we assume that this is a valid parameterless operation (as opposed to + * an operation with unknown parameters). We need this assumption because, contrarily to GeoAPI model, + * the GML schema does not differentiate "no parameters" from "unspecified parameters". + */ + private void afterUnmarshal(final Unmarshaller unmarshaller, final Object parent) { + if (parameters == null) { + parameters = CC_OperationMethod.group(super.getName(), new GeneralParameterDescriptor[0]); + } + } } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java index 06cc1f2de2..3478fbc723 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java @@ -114,7 +114,8 @@ final class MovingFeatureIterator extends FeatureIterator implements Consumer<Lo /** * Executes the given action for the next moving feature or for all remaining moving features. - * This method assumes that the 4 first columns are as documented in the code inside constructor. + * This method assumes that the 4 first columns are identifier, start time, end time and + * optional attributes in that order. * * @param action the action to execute as soon as the {@code mfidref} change, or {@code null} if none. * @param all {@code true} for executing the given action on all remaining features. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java index a1a0ef0750..a169618dbb 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java @@ -261,7 +261,7 @@ final class Store extends URIDataStore implements FeatureSet { throw new DataStoreContentException(Resources.forLocale(getLocale()) .getString(Resources.Keys.ShallBeDeclaredBefore_2, "@columns", "@stboundedby")); } - envelope = parseEnvelope(elements); // Also set 'timeEncoding' and 'spatialDimensionCount'. + envelope = parseEnvelope(elements); // Also set `timeEncoding` and `spatialDimensionCount`. dissociate |= (timeEncoding == null); // Need to be updated before parseFeatureType(…) execution. break; }