You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/01/12 01:56:14 UTC

[sis] branch geoapi-4.0 updated (9b45753 -> 45fcb32)

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

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.


    from 9b45753  Make the GPX tests more robust to coexistance of ESRI and JTS librairies on the classpath.
     new 1cca8c3  Add credits for https://issues.apache.org/jira/browse/SIS-532
     new c560a14  Add a safety check against `NullPointerException` if a concatenated operation contains a defining operation.
     new 97524f8  Make exception messages more informative for some errors happening at netCDF reading time. It requires some code for making possible to transfer information, but this is for error handling only.
     new 69fdbb4  Remove the `QuietLogRecord` package-private class, replaced by the use of a `java.util.logging.Filter`. It allows us to get the same functionality from other packages (in this commit, netCDF).
     new 45fcb32  Use the localization provided by `CompoundFormat` for formatting values in the metadata tree. This is a cleaner solution than previous code for solving a problem of geographic extent not property formatted.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/sis/gui/metadata/MetadataTree.java  |   4 +-
 .../sis/internal/gui/PropertyValueFormatter.java   |  16 +--
 .../org/apache/sis/internal/gui/PropertyView.java  | 114 +++++-------------
 .../org/apache/sis/internal/gui/TextFormats.java   | 132 +++++++++++++++++++++
 .../apache/sis/internal/referencing/Resources.java |  14 ++-
 .../sis/internal/referencing/Resources.properties  |   4 +-
 .../internal/referencing/Resources_fr.properties   |   4 +-
 .../referencing/factory/FactoryDataException.java  |  13 +-
 .../sis/referencing/factory/package-info.java      |   2 +-
 .../operation/DefaultConcatenatedOperation.java    |  12 +-
 .../operation/builder/LinearTransformBuilder.java  |  11 +-
 .../operation/builder/LocalizationGridBuilder.java |   2 +-
 .../builder/LocalizationGridException.java         | 114 ++++++++++++++++++
 .../operation/projection/ObliqueMercator.java      |   1 +
 .../operation/projection/ObliqueMercatorTest.java  |   1 +
 .../apache/sis/internal/util/PropertyFormat.java   |   4 +-
 .../main/java/org/apache/sis/util/Exceptions.java  |   5 +-
 .../apache/sis/util/resources/KeyConstants.java    |   2 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  |  16 +++
 .../org/apache/sis/internal/netcdf/Dimension.java  |  16 ++-
 .../java/org/apache/sis/internal/netcdf/Grid.java  |  12 +-
 .../org/apache/sis/internal/netcdf/Linearizer.java |  46 +++++--
 .../apache/sis/internal/netcdf/NamedElement.java   |   6 +-
 .../org/apache/sis/internal/netcdf/Variable.java   |  24 ++--
 .../sis/internal/netcdf/ucar/DimensionWrapper.java |  13 +-
 .../sis/internal/storage/AbstractResource.java     |  19 +++
 .../apache/sis/storage/event/QuietLogRecord.java   |  70 -----------
 .../apache/sis/storage/event/StoreListeners.java   |  59 +++++----
 .../org/apache/sis/storage/event/package-info.java |   3 +-
 29 files changed, 511 insertions(+), 228 deletions(-)
 create mode 100644 application/sis-javafx/src/main/java/org/apache/sis/internal/gui/TextFormats.java
 create mode 100644 core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.java
 delete mode 100644 storage/sis-storage/src/main/java/org/apache/sis/storage/event/QuietLogRecord.java

[sis] 01/05: Add credits for https://issues.apache.org/jira/browse/SIS-532

Posted by de...@apache.org.
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 1cca8c38ec4b2a1d084b8f07cf40e55d81b395ed
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Jan 10 16:51:05 2022 +0100

    Add credits for https://issues.apache.org/jira/browse/SIS-532
---
 .../org/apache/sis/referencing/operation/projection/ObliqueMercator.java | 1 +
 .../apache/sis/referencing/operation/projection/ObliqueMercatorTest.java | 1 +
 2 files changed, 2 insertions(+)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
index 7203e1f..8080965 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
@@ -55,6 +55,7 @@ import static org.apache.sis.internal.referencing.provider.ObliqueMercatorCenter
  * @author  Rueben Schulz (UBC)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
+ * @author  Emmanuel Giasson (Thales)
  * @version 1.2
  *
  * @see Mercator
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
index a3318ea..fc0f300 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
@@ -37,6 +37,7 @@ import static java.lang.StrictMath.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
+ * @author  Emmanuel Giasson (Thales)
  * @version 1.2
  * @since   1.0
  * @module

[sis] 05/05: Use the localization provided by `CompoundFormat` for formatting values in the metadata tree. This is a cleaner solution than previous code for solving a problem of geographic extent not property formatted.

Posted by de...@apache.org.
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 45fcb329921882cbe7a7d498a9aa6517a0d0547f
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Jan 12 02:55:10 2022 +0100

    Use the localization provided by `CompoundFormat` for formatting values in the metadata tree.
    This is a cleaner solution than previous code for solving a problem of geographic extent not property formatted.
---
 .../org/apache/sis/gui/metadata/MetadataTree.java  |   4 +-
 .../sis/internal/gui/PropertyValueFormatter.java   |  16 +--
 .../org/apache/sis/internal/gui/PropertyView.java  | 114 +++++-------------
 .../org/apache/sis/internal/gui/TextFormats.java   | 132 +++++++++++++++++++++
 .../apache/sis/internal/util/PropertyFormat.java   |   4 +-
 5 files changed, 175 insertions(+), 95 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
index 0effac0..c8c2558 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
@@ -372,7 +372,7 @@ check:      if (data != null) {
             final MetadataTree view = (MetadataTree) cell.getTreeTableView();
             Object value = getValue(cell, view.valueSourceColumn);
             if (value instanceof IdentifiedObject) {
-                value = IdentifiedObjects.getDisplayName((IdentifiedObject) value, locale);
+                value = IdentifiedObjects.getDisplayName((IdentifiedObject) value, getLocale());
             }
             try {
                 clear();
@@ -474,7 +474,7 @@ check:      if (data != null) {
      */
     private void showPropertyValue(final Object value) {
         if (propertyViewer == null) {
-            propertyViewer = new PropertyView(getLocale(), null, null);
+            propertyViewer = new PropertyView(formatter);
             propertyDialog = new Dialog<>();
             propertyDialog.setResizable(true);
             propertyDialog.setTitle(Resources.forLocale(getLocale()).getString(Resources.Keys.PropertyValue));
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyValueFormatter.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyValueFormatter.java
index c6adcd2..7d82d79 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyValueFormatter.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyValueFormatter.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.internal.gui;
 
-import java.util.Date;
 import java.util.Locale;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.CharSequences;
@@ -34,10 +33,10 @@ import org.apache.sis.internal.util.PropertyFormat;
  */
 public class PropertyValueFormatter extends PropertyFormat {
     /**
-     * The locale to use for objects. This is usually {@link Locale#getDefault()}.
-     * This value is given to {@link InternationalString#toString(Locale)} calls.
+     * The formats to use for objects. Its locale is usually {@link Locale#getDefault()}.
+     * The locale is also given to {@link InternationalString#toString(Locale)} calls.
      */
-    protected final Locale locale;
+    final TextFormats formats;
 
     /**
      * Creates a formatter for the specified locale.
@@ -47,8 +46,8 @@ public class PropertyValueFormatter extends PropertyFormat {
      */
     public PropertyValueFormatter(final Appendable buffer, final Locale locale) {
         super(buffer);
-        this.locale = locale;
         setLineSeparator(" ¶ ");
+        formats = new TextFormats(locale);
     }
 
     /**
@@ -56,7 +55,7 @@ public class PropertyValueFormatter extends PropertyFormat {
      */
     @Override
     public final Locale getLocale() {
-        return locale;
+        return formats.getLocale();
     }
 
     /**
@@ -65,8 +64,9 @@ public class PropertyValueFormatter extends PropertyFormat {
      */
     @Override
     protected String toString(final Object value) {
-        if (value instanceof Number || value instanceof Date) {             // See super-class javadoc.
-            return value.toString();
+        String text = formats.formatValue(value, false);
+        if (text != null) {
+            return text;
         }
         return Classes.getShortClassName(value) + "(…)";
     }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java
index 2a30407..69cdc19 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java
@@ -20,11 +20,6 @@ import java.util.Locale;
 import java.util.Objects;
 import java.util.Collection;
 import java.lang.reflect.Array;
-import java.io.IOException;
-import java.text.Format;
-import java.text.FieldPosition;
-import java.text.ParsePosition;
-import java.text.ParseException;
 import java.awt.Rectangle;
 import java.awt.image.RenderedImage;
 import javafx.beans.value.ChangeListener;
@@ -43,9 +38,8 @@ import javafx.scene.layout.GridPane;
 import javafx.scene.layout.Pane;
 import javafx.scene.layout.Priority;
 import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.io.CompoundFormat;
 import org.apache.sis.math.Statistics;
-import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.util.Localized;
 import org.apache.sis.util.resources.Vocabulary;
 
 
@@ -53,16 +47,20 @@ import org.apache.sis.util.resources.Vocabulary;
  * A viewer for property value. The property may be of various class (array, image, <i>etc</i>).
  * If the type is unrecognized, the property is shown as text.
  *
- * <p>This class extends {@link CompoundFormat} and implements {@code ChangeListener} for
- * implementation convenience only. Users should not rely on this implementation details.</p>
+ * <p>This class implements {@code ChangeListener} for implementation convenience only.
+ * Users should not rely on this implementation details.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.2
  * @since   1.1
  * @module
  */
-@SuppressWarnings({"serial","CloneableImplementsClone"})            // Not intended to be serialized.
-public final class PropertyView extends CompoundFormat<Object> implements ChangeListener<Number> {
+public final class PropertyView implements Localized, ChangeListener<Number> {
+    /**
+     * Provider for {@link java.text.NumberFormat}, {@link java.text.DateFormat}, <i>etc</i>.
+     */
+    private final TextFormats formats;
+
     /**
      * The current property value. This is used for detecting changes.
      */
@@ -131,18 +129,25 @@ public final class PropertyView extends CompoundFormat<Object> implements Change
     private ImageConverter runningTask, pendingTask;
 
     /**
-     * Creates a new property view.
+     * Creates a new property view which will use the given formatter for formatting values.
+     *
+     * @param  formatter  the formatter to use for formatting values.
+     */
+    public PropertyView(final PropertyValueFormatter formatter) {
+        formats = formatter.formats;
+        view = new SimpleObjectProperty<>(this, "view");
+    }
+
+    /**
+     * Creates a new property view which will set the node on the given property.
      *
      * @param  locale      the locale for numbers formatting.
-     * @param  view        the property where to set the node showing the value, or {@code null} for a default one.
+     * @param  view        the property where to set the node showing the value.
      * @param  background  the image background color, or {@code null} if none.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    public PropertyView(final Locale locale, ObjectProperty<Node> view, final ObjectProperty<Background> background) {
-        super(locale, null);
-        if (view == null) {
-            view = new SimpleObjectProperty<>(this, "view");
-        }
+    public PropertyView(final Locale locale, final ObjectProperty<Node> view, final ObjectProperty<Background> background) {
+        formats = new TextFormats(locale);
         this.view = view;
         if (background != null) {
             getImageCanvas().backgroundProperty().bind(background);
@@ -150,66 +155,13 @@ public final class PropertyView extends CompoundFormat<Object> implements Change
     }
 
     /**
-     * Required by {@link CompoundFormat} but not used.
-     *
-     * @return the base type of values formatted by this {@code PropertyView} instance.
-     */
-    @Override
-    public Class<? extends Object> getValueType() {
-        return Object.class;
-    }
-
-    /**
-     * Unsupported operation.
+     * Returns the locale for formatting messages and values.
      *
-     * @param  text ignored.
-     * @param  pos  ignored.
-     * @return never return.
-     * @throws ParseException always thrown.
+     * @return the locale used by formats.
      */
     @Override
-    public Object parse(CharSequence text, ParsePosition pos) throws ParseException {
-        throw new ParseException(null, 0);
-    }
-
-    /**
-     * Formats the given property value. Current implementation requires {@code toAppendTo}
-     * to be an instance of {@link StringBuffer}. This method is not intended to be invoked
-     * outside internal usage.
-     *
-     * @param  value       the property value to format.
-     * @param  toAppendTo  where to append the property value.
-     */
-    @Override
-    public void format(final Object value, final Appendable toAppendTo) throws IOException {
-        final Format f = getFormat(value.getClass());
-        if (f != null) {
-            f.format(value, (StringBuffer) toAppendTo, new FieldPosition(0));
-        } else {
-            toAppendTo.append(value.toString());
-        }
-    }
-
-    /**
-     * Formats a single value. This method does the same work than the inherited
-     * {@link #format(Object)} final method but in a more efficient way.
-     */
-    private String formatValue(final Object value) {
-        final Format f = getFormat(value.getClass());
-        if (f == null) {
-            return value.toString();
-        } else if (value instanceof Number) {
-            return Numerics.useScientificNotationIfNeeded(f, value, Format::format);
-        } else {
-            return f.format(value);
-        }
-    }
-
-    /**
-     * Formats the given value, using scientific notation if needed.
-     */
-    private static void format(final Format f, final double value, final StringBuffer buffer, final FieldPosition pos) {
-        Numerics.useScientificNotationIfNeeded(f, value, (nf,v) -> {nf.format(v, buffer, pos); return null;});
+    public Locale getLocale() {
+        return formats.getLocale();
     }
 
     /**
@@ -247,7 +199,7 @@ public final class PropertyView extends CompoundFormat<Object> implements Change
                 } else if (newValue.getClass().isArray()) {
                     content = setList(newValue);
                 } else {
-                    content = setText(formatValue(newValue));
+                    content = setText(formats.formatValue(newValue, true));
                 }
             }
             view.set(content);
@@ -288,7 +240,7 @@ public final class PropertyView extends CompoundFormat<Object> implements Change
         }
         final String[] list = new String[Array.getLength(array)];
         for (int i=0; i<list.length; i++) {
-            list[i] = formatValue(Array.get(array, i));
+            list[i] = formats.formatValue(Array.get(array, i), true);
         }
         listView.getItems().setAll(list);
         return node;
@@ -395,16 +347,12 @@ public final class PropertyView extends CompoundFormat<Object> implements Change
         String mean  = null;
         if (statistics != null && statistics.length != 0) {
             final Statistics s = statistics[0];
-            final FieldPosition pos = new FieldPosition(0);
             final StringBuffer buffer = new StringBuffer();
-            final Format f = getFormat(Number.class);
-            format(f, s.minimum(), buffer, pos); buffer.append(" … ");
-            format(f, s.maximum(), buffer, pos);
+            formats.formatPair(s.minimum(), " … ", s.maximum(), buffer);
             range = buffer.toString();
 
             buffer.setLength(0);
-            format(f, s.mean(), buffer, pos); buffer.append(" ± ");
-            format(f, s.standardDeviation(false), buffer, pos);
+            formats.formatPair(s.mean(), " ± ", s.standardDeviation(false), buffer);
 
             final Vocabulary vocabulary = Vocabulary.getResources(getLocale());
             buffer.append(" (").append(vocabulary.getString(Vocabulary.Keys.StandardDeviation)).append(')');
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/TextFormats.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/TextFormats.java
new file mode 100644
index 0000000..11ca158
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/TextFormats.java
@@ -0,0 +1,132 @@
+/*
+ * 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.internal.gui;
+
+import java.util.Locale;
+import java.util.TimeZone;
+import java.text.Format;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.ParseException;
+import java.io.IOException;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.io.CompoundFormat;
+
+
+/**
+ * A provider for {@link java.text.NumberFormat}, {@link java.text.DateFormat}, <i>etc</i>.
+ * Used for formatting values in {@link PropertyValueFormatter}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+@SuppressWarnings({"serial","CloneableImplementsClone"})            // Not intended to be serialized.
+final class TextFormats extends CompoundFormat<Object> {
+    /**
+     * Creates a new format for the given locale.
+     */
+    TextFormats(final Locale locale) {
+        super(locale, TimeZone.getDefault());
+    }
+
+    /**
+     * Required by {@link CompoundFormat} but not used.
+     *
+     * @return the base type of values formatted by this {@code PropertyView} instance.
+     */
+    @Override
+    public final Class<? extends Object> getValueType() {
+        return Object.class;
+    }
+
+    /**
+     * Unsupported operation.
+     *
+     * @param  text ignored.
+     * @param  pos  ignored.
+     * @return never return.
+     * @throws ParseException always thrown.
+     */
+    @Override
+    public Object parse(CharSequence text, ParsePosition pos) throws ParseException {
+        throw new ParseException(null, 0);
+    }
+
+    /**
+     * Formats the given property value. Current implementation requires {@code toAppendTo}
+     * to be an instance of {@link StringBuffer}. This method is not intended to be invoked
+     * outside internal usage.
+     *
+     * @param  value       the property value to format.
+     * @param  toAppendTo  where to append the property value.
+     */
+    @Override
+    public void format(final Object value, final Appendable toAppendTo) throws IOException {
+        final StringBuffer buffer = (StringBuffer) toAppendTo;
+        final Format f = getFormat(value.getClass());
+        if (f != null) {
+            f.format(value, buffer, new FieldPosition(0));
+        } else {
+            buffer.append(value);
+        }
+    }
+
+    /**
+     * Formats a single value. This method does the same work than the inherited
+     * {@link #format(Object)} final method but in a more efficient way.
+     *
+     * @param  value            the value to format.
+     * @param  toStringAllowed  whether to fallback on {@link Object#toString()} if there is no format
+     *         for the given object. If {@code false}, then {@code null} will be returned instead.
+     * @return formatted string representation of the given value.
+     */
+    public String formatValue(final Object value, final boolean toStringAllowed) {
+        final Format f = getFormat(value.getClass());
+        if (f == null) {
+            return value.toString();
+        } else if (value instanceof Number) {
+            return Numerics.useScientificNotationIfNeeded(f, value, Format::format);
+        } else {
+            return f.format(value);
+        }
+    }
+
+    /**
+     * Formats a pair of values in the given buffer. If can be used for formatting a range
+     * like "minimum … maximum" or for formatting a value with uncertainty like "mean ± std".
+     *
+     * @param  first       the first value of the range.
+     * @param  separator   string to insert between the two values.
+     * @param  second      the maximum value of the range.
+     * @param  toAppendTo  where to append the property value.
+     */
+    public void formatPair(final double first, final String separator, final double second, final StringBuffer toAppendTo) {
+        final FieldPosition pos = new FieldPosition(0);
+        final Format f = getFormat(Number.class);
+        format(f, first,  toAppendTo, pos); toAppendTo.append(separator);
+        format(f, second, toAppendTo, pos);
+    }
+
+    /**
+     * Formats the given value, using scientific notation if needed.
+     */
+    private static void format(final Format f, final double value, final StringBuffer buffer, final FieldPosition pos) {
+        Numerics.useScientificNotationIfNeeded(f, value, (nf,v) -> {nf.format(v, buffer, pos); return null;});
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java
index 3fbcecf..e8595a4 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/PropertyFormat.java
@@ -155,8 +155,8 @@ public abstract class PropertyFormat extends LineAppender implements Localized {
      * to be handled in a special way. Some of the types handled in a special way are {@link InternationalString},
      * {@link ControlledVocabulary}, {@link Enum}, {@link Type}, {@link Locale}, {@link TimeZone}, {@link Charset},
      * {@link Currency}, {@link Record}, {@link Iterable} and arrays. Other types should be handled by this method.
-     * In particular, {@link Number} and {@link java.util.Date} are <strong>not</strong> handled by default by this
-     * {@link PropertyFormat} class and should be handled here.
+     * In particular, {@link Number}, {@link java.util.Date} and {@link org.apache.sis.measure.Angle}
+     * are <strong>not</strong> handled by default by this {@link PropertyFormat} class and should be handled here.
      *
      * @param  value  the value to format (never {@code null}).
      * @return the formatted value.

[sis] 04/05: Remove the `QuietLogRecord` package-private class, replaced by the use of a `java.util.logging.Filter`. It allows us to get the same functionality from other packages (in this commit, netCDF).

Posted by de...@apache.org.
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 69fdbb413c721ea88e843bc3c8695d5845d234ef
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Jan 12 01:08:00 2022 +0100

    Remove the `QuietLogRecord` package-private class, replaced by the use of a `java.util.logging.Filter`.
    It allows us to get the same functionality from other packages (in this commit, netCDF).
---
 .../apache/sis/util/resources/KeyConstants.java    |  2 +-
 .../apache/sis/internal/netcdf/NamedElement.java   |  6 +-
 .../sis/internal/storage/AbstractResource.java     | 19 ++++++
 .../apache/sis/storage/event/QuietLogRecord.java   | 70 ----------------------
 .../apache/sis/storage/event/StoreListeners.java   | 59 +++++++++++-------
 .../org/apache/sis/storage/event/package-info.java |  3 +-
 6 files changed, 61 insertions(+), 98 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/KeyConstants.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/KeyConstants.java
index 8c7d199..4f46302 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/KeyConstants.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/KeyConstants.java
@@ -102,7 +102,7 @@ public class KeyConstants {
 
     /**
      * Returns the name of the key at the given index. If there is no name at that given
-     * index, format the index as a decimal number. Those decimal numbers are parsed by
+     * index, formats the index as a decimal number. Those decimal numbers are parsed by
      * our {@link IndexedResourceBundle#handleGetObject(String)} implementation.
      */
     final String getKeyName(final short index) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
index 2a0c9cb..a6f1e52 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
@@ -23,6 +23,7 @@ import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Strings;
+import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.resources.IndexedResourceBundle;
 
@@ -32,7 +33,7 @@ import org.apache.sis.util.resources.IndexedResourceBundle;
  * All those objects share in common a {@link #getName()} method.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.2
  * @since   0.8
  * @module
  */
@@ -113,10 +114,9 @@ public abstract class NamedElement {
         record.setSourceClassName(caller.getCanonicalName());
         record.setSourceMethodName(method);
         if (exception != null) {
-            // TODO: avoid reporting the full exception stack trace (maybe leverage QuietLogRecord).
             record.setThrown(exception);
         }
-        listeners.warning(record);
+        listeners.warning(record, AbstractResource.removeStackTraceInLogs());
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
index d38b603..8888341 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
@@ -18,6 +18,8 @@ package org.apache.sis.internal.storage;
 
 import java.util.Locale;
 import java.util.Optional;
+import java.util.logging.Filter;
+import java.util.logging.LogRecord;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Metadata;
@@ -215,4 +217,21 @@ public class AbstractResource extends StoreListeners implements Resource {
             super.addListener(eventType, listener);
         }
     }
+
+    /**
+     * Returns a log filter that removes the stack trace of filtered given log.
+     * It can be used as argument in a call to {@link StoreListeners#warning(LogRecord, Filter)}
+     * if the caller was to trim the stack trace in log files or console outputs.
+     *
+     * <p>This filter should be used only for filtering {@link LogRecord} created by the caller, because
+     * it modifies the record. Users would not expect this side effect on records created by them.</p>
+     *
+     * @return a filter for trimming stack trace.
+     */
+    public static Filter removeStackTraceInLogs() {
+        return (record) -> {
+            record.setThrown(null);
+            return true;
+        };
+    }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/QuietLogRecord.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/QuietLogRecord.java
deleted file mode 100644
index 06a3496..0000000
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/QuietLogRecord.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.storage.event;
-
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-
-
-/**
- * A log record to be logged without stack trace, unless the user specified it explicitly.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   0.3
- * @module
- */
-final class QuietLogRecord extends LogRecord {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 5652099235767670922L;
-
-    /**
-     * {@code true} if the user invoked {@link #setThrown(Throwable)} explicitly.
-     * In such case, {@link #clearImplicitThrown()} will not reset the throwable to null.
-     */
-    private boolean explicitThrown;
-
-    /**
-     * Creates a new log record for the given message and exception.
-     */
-    QuietLogRecord(final Level level, final String message, final Exception exception) {
-        super(level, message);
-        super.setThrown(exception);
-    }
-
-    /**
-     * Sets the throwable to the given value. The given throwable will not be cleared
-     * when the record will be logged.
-     */
-    @Override
-    public void setThrown(final Throwable thrown) {
-        explicitThrown = true;
-        super.setThrown(thrown);
-    }
-
-    /**
-     * Clears the throwable if it has not been explicitly set by the user.
-     * Otherwise do nothing.
-     */
-    void clearImplicitThrown() {
-        if (!explicitThrown) {
-            super.setThrown(null);
-        }
-    }
-}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
index 8943dc6..e790510 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
@@ -22,6 +22,7 @@ import java.util.IdentityHashMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
+import java.util.logging.Filter;
 import java.lang.reflect.Method;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Localized;
@@ -49,8 +50,8 @@ import org.apache.sis.storage.Resource;
  * and additional information like {@linkplain LogRecord#getThrown() stack trace}, timestamp, <i>etc.</i>).
  * This {@code StoreListeners} class provides convenience methods like {@link #warning(String, Exception)},
  * which build {@code LogRecord} from an exception or from a string. But all those {@code warning(…)} methods
- * ultimately delegate to {@link #warning(LogRecord)}, thus providing a single point that subclasses can override.
- * When a warning is emitted, the default behavior is:
+ * ultimately delegate to {@link #warning(LogRecord, Filter)}, thus providing a single point that subclasses
+ * can override. When a warning is emitted, the default behavior is:
  *
  * <ul>
  *   <li>Notify all listeners that are registered for a given {@link WarningEvent} type in this {@code StoreListeners}
@@ -73,7 +74,7 @@ import org.apache.sis.storage.Resource;
  * from multiple threads.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.0
  * @module
  */
@@ -397,16 +398,11 @@ public class StoreListeners implements Localized {
      * If both are non-null, then the exception message will be concatenated after the given message.
      *
      * <h4>Stack trace omission</h4>
-     * If there is no registered listener for the {@link WarningEvent} type, then the {@link #warning(LogRecord)}
-     * method will send the record to a logger but <em>without</em> the stack trace.
+     * If there is no registered listener for the {@link WarningEvent} type,
+     * then the {@link LogRecord} will be sent to a {@link Logger} but <em>without</em> the stack trace.
      * This is done that way because stack traces consume lot of space in the logging files, while being considered
      * implementation details in the context of {@code StoreListeners} (on the assumption that the logging message
-     * provides sufficient information). If the stack trace is desired, then users can either:
-     * <ul>
-     *   <li>invoke {@code warning(LogRecord)} directly, or</li>
-     *   <li>override {@code warning(LogRecord)} and invoke {@link LogRecord#setThrown(Throwable)} explicitly, or</li>
-     *   <li>register a listener which will log the record itself.</li>
-     * </ul>
+     * provides sufficient information).
      *
      * @param  level      the warning level.
      * @param  message    the message to log, or {@code null} if none.
@@ -422,7 +418,8 @@ public class StoreListeners implements Localized {
             if (message == null) {
                 message = exception.toString();
             }
-            record = new QuietLogRecord(level, message, exception);
+            record = new LogRecord(level, message);
+            record.setThrown(exception);
         } else {
             ArgumentChecks.ensureNonEmpty("message", message);
             trace = Thread.currentThread().getStackTrace();         // TODO: on JDK9, use StackWalker instead.
@@ -437,7 +434,7 @@ public class StoreListeners implements Localized {
         } catch (ClassNotFoundException | SecurityException e) {
             Logging.ignorableException(StoreUtilities.LOGGER, StoreListeners.class, "warning", e);
         }
-        warning(record);
+        warning(record, AbstractResource.removeStackTraceInLogs());
     }
 
     /**
@@ -464,28 +461,47 @@ public class StoreListeners implements Localized {
     }
 
     /**
+     * Reports a warning described by the given log record. Invoking this method is
+     * equivalent to invoking {@link #warning(LogRecord, Filter)} with a null filter.
+     *
+     * @param  description  warning details provided as a log record.
+     */
+    public void warning(final LogRecord description) {
+        warning(description, null);
+    }
+
+    /**
      * Reports a warning described by the given log record. The default implementation forwards
      * the given record to one of the following destinations, in preference order:
      *
-     * <ul>
+     * <ol>
      *   <li><code>{@linkplain StoreListener#eventOccured StoreListener.eventOccured}(new
      *       {@linkplain WarningEvent}(source, record))</code> on all listeners registered for this kind of event.</li>
-     *   <li>Only if above step found no listener, then <code>{@linkplain Logging#getLogger(String)
-     *       Logging.getLogger}(record.loggerName).{@linkplain Logger#log(LogRecord) log}(record)</code>
-     *       where {@code loggerName} is one of the following:
+     *   <li><code>{@linkplain Filter#isLoggable(LogRecord) onUnhandled.isLoggable(description)}</code>
+     *       if above step found no listener and the {@code onUnhandled} filter is non-null.</li>
+     *   <li><code>{@linkplain Logger#getLogger(String)
+     *       Logger.getLogger}(record.loggerName).{@linkplain Logger#log(LogRecord) log}(record)</code>
+     *       if the filter in above step returned {@code true} (or if the filter is null).
+     *       In that case, {@code loggerName} is one of the following:
      *     <ul>
      *       <li><code>record.{@linkplain LogRecord#getLoggerName() getLoggerName()}</code> if that value is non-null.</li>
      *       <li>Otherwise the value of {@link DataStoreProvider#getLogger()} if the provider is found.</li>
      *       <li>Otherwise the source {@link DataStore} package name.</li>
      *     </ul>
      *   </li>
-     * </ul>
+     * </ol>
      *
      * @param  description  warning details provided as a log record.
+     * @param  onUnhandled  filter invoked if the record has not been handled by a {@link StoreListener},
+     *                      or {@code null} if none.
+     *
+     * @since 1.2
      */
     @SuppressWarnings("unchecked")
-    public void warning(final LogRecord description) {
-        if (!fire(new WarningEvent(source, description), WarningEvent.class)) {
+    public void warning(final LogRecord description, final Filter onUnhandled) {
+        if (!fire(new WarningEvent(source, description), WarningEvent.class) &&
+                (onUnhandled == null || onUnhandled.isLoggable(description)))
+        {
             final String name = description.getLoggerName();
             final Logger logger;
             if (name != null) {
@@ -494,9 +510,6 @@ public class StoreListeners implements Localized {
                 logger = getLogger();
                 description.setLoggerName(logger.getName());
             }
-            if (description instanceof QuietLogRecord) {
-                ((QuietLogRecord) description).clearImplicitThrown();
-            }
             logger.log(description);
         }
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/package-info.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/package-info.java
index c9a46b7..f8a7aaa 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/package-info.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/package-info.java
@@ -34,7 +34,8 @@
  * to a specific kind of event}.
  *
  * @author  Johann Sorel (Geomatys)
- * @since   1.1
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   1.2
  * @version 1.0
  * @module
  */

[sis] 03/05: Make exception messages more informative for some errors happening at netCDF reading time. It requires some code for making possible to transfer information, but this is for error handling only.

Posted by de...@apache.org.
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 97524f8379a6e1e80765793a875bd611230fd438
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Jan 11 22:37:23 2022 +0100

    Make exception messages more informative for some errors happening at netCDF reading time.
    It requires some code for making possible to transfer information, but this is for error handling only.
---
 .../apache/sis/internal/referencing/Resources.java |   9 +-
 .../sis/internal/referencing/Resources.properties  |   3 +-
 .../internal/referencing/Resources_fr.properties   |   3 +-
 .../referencing/factory/FactoryDataException.java  |  13 ++-
 .../sis/referencing/factory/package-info.java      |   2 +-
 .../operation/builder/LinearTransformBuilder.java  |  11 +-
 .../operation/builder/LocalizationGridBuilder.java |   2 +-
 .../builder/LocalizationGridException.java         | 114 +++++++++++++++++++++
 .../main/java/org/apache/sis/util/Exceptions.java  |   5 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  |  16 +++
 .../org/apache/sis/internal/netcdf/Dimension.java  |  16 ++-
 .../java/org/apache/sis/internal/netcdf/Grid.java  |  12 ++-
 .../org/apache/sis/internal/netcdf/Linearizer.java |  46 +++++++--
 .../org/apache/sis/internal/netcdf/Variable.java   |  24 +++--
 .../sis/internal/netcdf/ucar/DimensionWrapper.java |  13 ++-
 15 files changed, 257 insertions(+), 32 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
index b3e451d..36943bc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
@@ -44,7 +44,7 @@ public final class Resources extends IndexedResourceBundle {
      * pools of compiled classes.
      *
      * @author  Martin Desruisseaux (IRD, Geomatys)
-     * @since   0.8
+     * @since   1.2
      * @module
      */
     public static final class Keys extends KeyConstants {
@@ -189,7 +189,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short ConstantProjParameterValue_1 = 12;
 
         /**
-         * Coordinate conversion of transformation from system “{0}” to “{1}” has not been found.
+         * Coordinate operation from system “{0}” to “{1}” has not been found.
          */
         public static final short CoordinateOperationNotFound_2 = 13;
 
@@ -247,6 +247,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short GeodeticDataBase_4 = 18;
 
         /**
+         * The grid spans {0}° of longitude, which may be too wide for the “{1}” domain.
+         */
+        public static final short GridLongitudeSpanTooWide_2 = 102;
+
+        /**
          * More than one service provider of type ‘{0}’ are declared for “{1}”. Only the first provider
          * (an instance of ‘{2}’) will be used.
          */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
index 59a3282..17b98e8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
@@ -66,7 +66,7 @@ CanNotTransformEnvelopeToGeodetic = Can not transform envelope to a geodetic ref
 CanNotTransformGeometry           = Can not transform the given geometry.
 CanNotUseGeodeticParameters_2     = Can not use the {0} geodetic parameters. Caused by: {1}
 ColinearAxisDirections_2          = Axis directions {0} and {1} are colinear.
-CoordinateOperationNotFound_2     = Coordinate conversion of transformation from system \u201c{0}\u201d to \u201c{1}\u201d has not been found.
+CoordinateOperationNotFound_2     = Coordinate operation from system \u201c{0}\u201d to \u201c{1}\u201d has not been found.
 DatumChangesDirectory_1           = Datum shift files are searched in the \u201c{0}\u201d directory.
 DatumOriginShallBeDate            = Origin of temporal datum shall be a date.
 DuplicatedParameterName_4         = Name or alias for parameter \u201c{0}\u201d at index {1} conflict with name \u201c{2}\u201d at index {3}.
@@ -74,6 +74,7 @@ DuplicatedSpatialComponents_1     = Compound coordinate reference systems can no
 EllipsoidalHeightNotAllowed_1     = Compound coordinate reference systems should not contain ellipsoidal height. Use a three-dimensional {0,choice,0#geographic|1#projected} system instead.
 FileNotFound_2                    = Can not find {0} file named \u201c{1}\u201d.
 FileNotReadable_2                 = Can not parse \u201c{1}\u201d as a file in the {0} format.
+GridLongitudeSpanTooWide_2        = The grid spans {0}\u00b0 of longitude, which may be too wide for the \u201c{1}\u201d domain.
 IllegalAxisDirection_2            = Coordinate system of class \u2018{0}\u2019 can not have axis in the {1} direction.
 IllegalOperationDimension_3       = Dimensions of \u201c{0}\u201d operation can not be ({1} \u2192 {2}).
 IllegalOperationForValueClass_1   = This operation can not be applied to values of class \u2018{0}\u2019.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
index 6ef6d37..e81f00e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
@@ -71,7 +71,7 @@ CanNotTransformEnvelopeToGeodetic = Ne peut pas transformer l\u2019enveloppe ver
 CanNotTransformGeometry           = Ne peut pas transformer la g\u00e9om\u00e9trie donn\u00e9e.
 CanNotUseGeodeticParameters_2     = Ne peut pas utiliser les param\u00e8tres g\u00e9od\u00e9siques {0}. La cause est\u202f: {1}
 ColinearAxisDirections_2          = Les directions d\u2019axes {0} et {1} sont colin\u00e9aires.
-CoordinateOperationNotFound_2     = La conversion ou transformation des coordonn\u00e9es du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb vers \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e.
+CoordinateOperationNotFound_2     = L\u2019op\u00e9ration sur les coordonn\u00e9es du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb vers \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e.
 DatumChangesDirectory_1           = Les fichiers de changements de r\u00e9f\u00e9rentiel sont cherch\u00e9s dans le dossier \u00ab\u202f{0}\u202f\u00bb.
 DatumOriginShallBeDate            = L\u2019origine d\u2019un r\u00e9f\u00e9rentiel temporel doit \u00eatre une date.
 DuplicatedParameterName_4         = Le nom ou un alias pour le param\u00e8tre \u00ab\u202f{0}\u202f\u00bb \u00e0 l\u2019index {1} duplique le nom \u00ab\u202f{2}\u202f\u00bb \u00e0 l\u2019index {3}.
@@ -79,6 +79,7 @@ DuplicatedSpatialComponents_1     = Un syst\u00e8me de r\u00e9f\u00e9rence des c
 EllipsoidalHeightNotAllowed_1     = Un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es ne devrait pas contenir une hauteur ellipso\u00efdale. Utilisez plut\u00f4t un syst\u00e8me {0,choice,0#g\u00e9ographique|1#projet\u00e9} \u00e0 trois dimensions.
 FileNotFound_2                    = Ne peut pas trouver le fichier {0} nomm\u00e9 \u00ab\u202f{1}\u202f\u00bb.
 FileNotReadable_2                 = Ne peut pas lire \u00ab\u202f{1}\u202f\u00bb comme un fichier au format {0}.
+GridLongitudeSpanTooWide_2        = La grille s\u2019\u00e9tend sur {0}\u00b0 de longitude, ce qui peut \u00eatre trop pour le domaine de \u00ab\u202f{1}\u202f\u00bb.
 IllegalAxisDirection_2            = Les syst\u00e8mes de coordonn\u00e9es de classe \u2018{0}\u2019 ne peuvent pas avoir d\u2019axe dans la direction \u00ab\u202f{1}\u202f\u00bb.
 IllegalOperationDimension_3       = Les dimensions de l\u2019op\u00e9ration \u00ab\u202f{0}\u202f\u00bb ne peuvent pas \u00eatre ({1} \u2192 {2}).
 IllegalOperationForValueClass_1   = Cette op\u00e9ration ne peut pas s\u2019appliquer aux valeurs de classe \u2018{0}\u2019.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java
index 873387b..2cd38c8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java
@@ -26,7 +26,7 @@ import org.opengis.util.FactoryException;
  * an EPSG database record containing a null value in a column where nulls should not have been allowed.</div>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.2
  * @since   0.7
  * @module
  */
@@ -52,6 +52,17 @@ public class FactoryDataException extends FactoryException {
     }
 
     /**
+     * Constructs an exception with the specified cause.
+     *
+     * @param cause  the cause, saved for later retrieval by the {@link #getCause()} method.
+     *
+     * @since 1.2
+     */
+    public FactoryDataException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
      * Construct an exception with the specified detail message and cause.
      *
      * @param  message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
index affc539..0c2c551 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
@@ -56,7 +56,7 @@
  * </table>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 1.2
  * @since   0.6
  * @module
  */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
index 0f5838f..36ee404 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
@@ -270,7 +270,7 @@ public class LinearTransformBuilder extends TransformBuilder {
         try {
             return (LinearTransform) Linearizer.approximate(gridToCRS, domain);
         } catch (TransformException e) {
-            throw new FactoryException(e);
+            throw new LocalizationGridException(e);
         }
     }
 
@@ -1467,10 +1467,13 @@ search:         for (int j=domain(); --j >= 0;) {
                 }
                 /*
                  * Finished to try all transforms. If all of them failed, wrap the `TransformException`.
+                 * We use a sub-type of `FactoryException` which allows callers to add their own information.
+                 * For example the caller may know that the grid was possibly out of CRS domain of validity
+                 * and wanted to try anyway (it can be difficult to predict in advance if it will work).
                  */
                 if (bestTransform == null) {
-                    throw new FactoryException(Resources.format(Resources.Keys.CanNotLinearizeLocalizationGrid),
-                                               ProjectedTransformTry.getError(linearizers));
+                    throw new LocalizationGridException(Resources.format(Resources.Keys.CanNotLinearizeLocalizationGrid),
+                                                        ProjectedTransformTry.getError(linearizers));
                 }
                 if (needTargetReplace) {
                     transformedArrays = appliedLinearizer.replaceTransformed(targets, transformedArrays);
@@ -1547,7 +1550,7 @@ search:         for (int j=domain(); --j >= 0;) {
                     break;
                 }
                 default: {
-                    throw new FactoryException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, sourceDim));
+                    throw new InvalidGeodeticParameterException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, sourceDim));
                 }
             }
             correlations[j] = c;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
index 73383a1..aaedbe2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
@@ -713,7 +713,7 @@ public class LocalizationGridBuilder extends TransformBuilder {
                         step = InterpolatedTransform.createGeodeticTransformation(nonNull(factory), shifts);
                     }
                 } catch (TransformException e) {
-                    throw new FactoryException(e);                                          // Should never happen.
+                    throw new LocalizationGridException(e);                                 // Should never happen.
                 }
             }
             /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.java
new file mode 100644
index 0000000..a74c47a
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.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.referencing.operation.builder;
+
+import org.opengis.util.InternationalString;
+import org.apache.sis.referencing.factory.FactoryDataException;
+
+
+/**
+ * Thrown when a localization grid can not be computed, presumably because of a problem with grid data.
+ * It may be because some grid coordinates are out of CRS domain of validity, causing either
+ * {@link org.opengis.referencing.operation.MathTransform} to be thrown or {@link Double#NaN}
+ * coordinate values to be computed.
+ *
+ * <h2>Additional information on exception cause</h2>
+ * It is sometime difficult to determine the root cause of this exception.
+ * For example grid points slightly outside the CRS domain of validity will not necessarily cause a failure.
+ * A strategy can be to try to build the grid anyway, and in case of failure declare that the grid was maybe
+ * too far from CRS domain of validity. Because the potential causes are better known by the code that wants
+ * a localization grid instead of the {@link LocalizationGridBuilder} class, {@code LocalizationGridException}
+ * provides a {@link #setPotentialCause(CharSequence)} method for allowing top-level code to attach additional
+ * information to this exception.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ */
+public class LocalizationGridException extends FactoryDataException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -9069664783475360076L;
+
+    /**
+     * Additional information about what may be the cause of this exception.
+     * Example: <cite>"The grid spans more than 180° of longitude"</cite>,
+     * which may be a cause of map projection failures.
+     *
+     * @see #getPotentialCause()
+     */
+    private CharSequence potentialCause;
+
+    /**
+     * Construct an exception with no detail message.
+     */
+    public LocalizationGridException() {
+    }
+
+    /**
+     * Constructs an exception with the specified detail message.
+     *
+     * @param message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
+     */
+    public LocalizationGridException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs an exception with the specified cause.
+     *
+     * @param cause  the cause, saved for later retrieval by the {@link #getCause()} method.
+     */
+    public LocalizationGridException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs an exception with the specified detail message and cause.
+     * The cause is the exception thrown in the underlying database
+     * (e.g. {@link java.io.IOException} or {@link java.sql.SQLException}).
+     *
+     * @param message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
+     * @param cause    the cause, saved for later retrieval by the {@link #getCause()} method.
+     */
+    public LocalizationGridException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Specifies additional information about what may be the cause of this exception.
+     * Example: <cite>"The grid spans more than 180° of longitude"</cite>,
+     * which may be a cause of map projection failures.
+     *
+     * @param  details  a potential cause, or {@code null} if none.
+     *         The type should be {@link String} or {@link InternationalString}.
+     */
+    public synchronized void setPotentialCause(CharSequence details) {
+        potentialCause = details;
+    }
+
+    /**
+     * Returns the value given to the last call of {@link #setPotentialCause(CharSequence)}.
+     *
+     * @return potential cause, or {@code null} if none.
+     *         The type should be {@link String} or {@link InternationalString}.
+     */
+    public synchronized CharSequence getPotentialCause() {
+        return potentialCause;
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java b/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
index 1ed6359..13a90a3 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
@@ -108,7 +108,10 @@ public final class Exceptions extends Static {
      *                   will be happened after the provided message.
      * @return a new exception with the given message, or the given exception if the exception
      *         class does not provide public {@code Exception(String)} constructor.
+     *
+     * @deprecated To be removed with no replacement.
      */
+    @Deprecated
     @SuppressWarnings("unchecked")
     public static <T extends Throwable> T setMessage(final T exception, String message, final boolean append) {
         if (append) {
@@ -155,7 +158,7 @@ public final class Exceptions extends Static {
     }
 
     /**
-     * Returns a string which contain the given message on the first line, followed by the
+     * Returns a string which contains the given message on the first line, followed by the
      * {@linkplain #getLocalizedMessage(Throwable, Locale) localized message} of the given exception
      * on the next line. If the exception has a {@linkplain Throwable#getCause() causes}, then
      * the class name and the localized message of the cause are formatted on the next line
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index a14466f..0b5c1bf 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -39,6 +39,7 @@ import org.opengis.referencing.operation.TransformException;
 import org.opengis.metadata.content.TransferFunctionType;
 import org.apache.sis.internal.referencing.AxisDirections;
 import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.referencing.operation.builder.LocalizationGridException;
 import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
 import org.apache.sis.referencing.operation.transform.TransferFunction;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -864,6 +865,21 @@ public final class Axis extends NamedElement {
                 tr = new GridCacheValue(linearizers, grid, factory);
                 tr = keyLocal.cache(decoder, tr);
             }
+        } catch (LocalizationGridException ex) {
+            /*
+             * Complete the exception with a possible failure cause before to propagate the exception.
+             * Example: "The grid spans more than 180° of longitude", which may cause transform errors.
+             * The possible causes are known only by the linearizer, which is why we could not set it
+             * at `LocalizationGridException` construction time.
+             */
+            for (final Linearizer linearizer : linearizers) {
+                final CharSequence reason = linearizer.getPotentialCause(coordinates);
+                if (reason != null) {
+                    ex.setPotentialCause(reason);
+                    break;          // Take the cause of the linearizer that had the highest priority.
+                }
+            }
+            throw ex;
         } finally {
             handler.putAndUnlock(tr);
         }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java
index 26caff7..f04687f 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java
@@ -32,7 +32,7 @@ import org.apache.sis.util.resources.Vocabulary;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.2
  * @since   1.0
  * @module
  */
@@ -71,6 +71,20 @@ public abstract class Dimension extends NamedElement {
     protected abstract boolean isUnlimited();
 
     /**
+     * Returns a dimension with its index decremented by 1. This method is invoked for trailing dimensions
+     * after a previous dimension has been removed from a list. This is useful only for subclasses that
+     * need to know the index of this dimension in a list of dimensions.
+     *
+     * <p>Note: this method may be removed in a future version if we do not need to store index anymore.
+     * See <a href="https://github.com/Unidata/netcdf-java/issues/951">Issue #951 on netcdf-java</a>.</p>
+     *
+     * @return a dimension equals to this one but with its list index (if any) decremented.
+     */
+    protected Dimension decrementIndex() {
+        return this;
+    }
+
+    /**
      * Writes in the given buffer the length of this dimension between bracket.
      * The length may be unknown (represented by {@code '?'}).
      */
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
index d19b8ce..374d625 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
@@ -30,6 +30,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.apache.sis.internal.referencing.AxisDirections;
 import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.builder.LocalizationGridException;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.IllegalGridGeometryException;
@@ -47,7 +48,7 @@ import org.apache.sis.util.ArraysExt;
  * if a variable dimensions should considered as bands instead of spatiotemporal dimensions.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see Decoder#getGrids()
  *
@@ -563,6 +564,13 @@ findFree:       for (int srcDim : axis.gridDimensionIndices) {
      * @param  key     one of {@link Resources.Keys#CanNotCreateCRS_3} or {@link Resources.Keys#CanNotCreateGridGeometry_3}.
      */
     private void canNotCreate(final Decoder decoder, final String caller, final short key, final Exception ex) {
-        warning(decoder.listeners, Grid.class, caller, ex, null, key, decoder.getFilename(), getName(), ex.getLocalizedMessage());
+        CharSequence message = null;
+        if (ex instanceof LocalizationGridException) {
+            message = ((LocalizationGridException) ex).getPotentialCause();
+        }
+        if (message == null) {
+            message = ex.getLocalizedMessage();
+        }
+        warning(decoder.listeners, Grid.class, caller, ex, null, key, decoder.getFilename(), getName(), message);
     }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
index 48ff2d6..d02505b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
@@ -21,6 +21,7 @@ import java.util.Map;
 import java.util.HashMap;
 import java.util.List;
 import org.opengis.geometry.Envelope;
+import org.opengis.util.InternationalString;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -161,6 +162,14 @@ public final class Linearizer {
     private boolean axisSwap;
 
     /**
+     * The image span in degrees of longitude, or 0 if not computed.
+     * This is used for giving a hint about why a projection may have failed.
+     *
+     * @see #getPotentialCause()
+     */
+    private float longitudeSpan;
+
+    /**
      * Creates a new linearizer working on the specified datum.
      *
      * @param  datum  the datum to use. Should be consistent with {@link Convention#defaultHorizontalCRS(boolean)}.
@@ -197,6 +206,23 @@ public final class Linearizer {
     }
 
     /**
+     * If this linearizer can give a probable reason why it failed to compute the localization grid, returns that reason.
+     * Otherwise returns {@code null}.
+     *
+     * @param  owner  for fetching localized resources.
+     * @return potential error cause, or {@code null} if unknown.
+     */
+    final InternationalString getPotentialCause(final Node owner) {
+        if (longitudeSpan >= 180 - 6) {         // 180° of longitude minus a UTM zone width.
+            final String name = IdentifiedObjects.getDisplayName(targetCRS, owner.getLocale());
+            return org.apache.sis.internal.referencing.Resources.formatInternational(
+                   org.apache.sis.internal.referencing.Resources.Keys.GridLongitudeSpanTooWide_2,
+                   longitudeSpan, (name != null) ? name : type);
+        }
+        return null;
+    }
+
+    /**
      * Returns a string representation for debugging purposes.
      */
     @Override
@@ -231,26 +257,32 @@ public final class Linearizer {
              */
             case UNIVERSAL: {
                 final Envelope bounds = grid.getSourceEnvelope(false);
-                double x, y, ymin, ymax;
+                double x, y, xmin, xmax, ymin, ymax;
                 {   // For keeping `median` variable local.
                     final double[] median = grid.getControlPoint(
                             (int) Math.round(bounds.getMedian(0)),
                             (int) Math.round(bounds.getMedian(1)));
-                    x = median[xdim];
-                    y = median[ydim];
-                    ymin = ymax = y;
+                    x = median[xdim]; xmin = xmax = x;
+                    y = median[ydim]; ymin = ymax = y;
                 }
                 final int[] gc = new int[SOURCE_DIMENSION];
                 for (int i=0; i<4; i++) {
                     for (int d=0; d<SOURCE_DIMENSION; d++) {
                         gc[d] = (int) Math.round(((i & (1 << d)) == 0) ? bounds.getMinimum(d) : bounds.getMaximum(d));
                     }
-                    final double yp = grid.getControlPoint(gc[0], gc[1])[ydim];
-                    if (yp < ymin) ymin = yp;
-                    if (yp > ymax) ymax = yp;
+                    final double[] cp = grid.getControlPoint(gc[0], gc[1]);
+                    double c = cp[xdim];
+                    if (c < xmin) xmin = c;
+                    if (c > xmax) xmax = c;
+                    c = cp[ydim];
+                    if (c < ymin) ymin = c;
+                    if (c > ymax) ymax = c;
                 }
+                longitudeSpan = (float) (xmax - xmin);      // For providing a hint in case of failure.
                 /*
                  * If the image is far from equator, replace the middle point by a point close to pole.
+                 * The intend is to avoid using UTM projection for latitudes such as 89°N, because a single
+                 * NaN in transformed coordinates is enough for blocking creation of the localization grid.
                  */
                      if (ymin >= +Type.POLAR_THRESHOLD) y = ymax;
                 else if (ymax <= -Type.POLAR_THRESHOLD) y = ymin;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
index 55b301a..9de8f12 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
@@ -390,7 +390,7 @@ public abstract class Variable extends Node {
     public final Unit<?> getUnit() {
         if (!unitParsed) {
             unitParsed = true;                          // Set first for avoiding to report errors many times.
-            final String symbols = getUnitsString();
+            String symbols = getUnitsString();
             Exception error = null;
             if (symbols != null) try {
                 unit = parseUnit(symbols);
@@ -402,6 +402,9 @@ public abstract class Variable extends Node {
             } catch (ParserException ex) {
                 if (error == null) error = ex;
                 else error.addSuppressed(ex);
+                if (symbols == null) {
+                    symbols = ex.getParsedString();
+                }
             }
             if (error != null) {
                 error(Variable.class, "getUnit", error, Errors.Keys.CanNotAssignUnitToVariable_2, getName(), symbols);
@@ -581,8 +584,8 @@ public abstract class Variable extends Node {
         }
         /*
          * Get all dimensions of this variable in netCDF order, then replace them by dimensions from an axis variable.
-         * If we are in the situation #1 documented in javadoc, 'isIncomplete' will be 'false' after execution of this
-         * loop and all dimensions should be the same than the values returned by 'Variable.getGridDimensions()'.
+         * If we are in the situation #1 documented in javadoc, `isIncomplete` will be `false` after execution of this
+         * loop and all dimensions should be the same than the values returned by `Variable.getGridDimensions()`.
          */
         boolean isIncomplete = false;
         final List<Dimension> fromVariable = getGridDimensions();
@@ -605,7 +608,7 @@ public abstract class Variable extends Node {
                     }
                     /*
                      * The first time that we find a label that may allow us to associate this variable dimension with a
-                     * grid dimension, build a map of all labels associated to dimensions. We reuse the existing 'domain'
+                     * grid dimension, build a map of all labels associated to dimensions. We reuse the existing `domain`
                      * map; there is no confusion since the keys are not of the same class.
                      */
                     if (isIncomplete) {
@@ -702,10 +705,10 @@ public abstract class Variable extends Node {
                         Dimension expected = toKeep.get(i);
                         expected = adjustment.gridToVariable.getOrDefault(expected, expected);
                         /*
-                         * At this point, 'expected' is a dimension of the variable that we expect to find at
-                         * current index 'i'. If we do not find that dimension, then the unexpected dimension
+                         * At this point, `expected` is a dimension of the variable that we expect to find at
+                         * current index `i`. If we do not find that dimension, then the unexpected dimension
                          * is assumed to be a band. We usually remove at most one element. If removal results
-                         * in a list too short, it would be a bug in the way we computed 'toKeep'.
+                         * in a list too short, it would be a bug in the way we computed `toKeep`.
                          */
                         while (!expected.equals(dimensions.get(i))) {
                             if (!copied) {
@@ -716,17 +719,20 @@ public abstract class Variable extends Node {
                              * It is possible that we never reach this point if the unexpected dimension is last.
                              * However in such case the dimension to declare is the last one in netCDF order,
                              * which corresponds to the first dimension (i.e. dimension 0) in "natural" order.
-                             * Since the 'bandDimension' field is initialized to zero, its value is correct.
+                             * Since the `bandDimension` field is initialized to zero, its value is correct.
                              */
                             bandDimension = dataDimension - 1 - i;          // Convert netCDF order to "natural" order.
                             dimensions.remove(i);
+                            for (int j = dimensions.size(); --j >= i;) {
+                                dimensions.set(j, dimensions.get(j).decrementIndex());
+                            }
                             if (dimensions.size() < numToKeep) {
                                 throw new InternalDataStoreException();     // Should not happen (see above comment).
                             }
                         }
                     }
                     /*
-                     * At this point 'dimensions' may still be longer than 'toKeep' but it does not matter.
+                     * At this point `dimensions` may still be longer than `toKeep` but it does not matter.
                      * We only need that for any index i < numToKeep, dimensions.get(i) corresponds to the
                      * dimension at the same index in the grid.
                      */
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java
index caa7c54..579e24e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java
@@ -69,7 +69,7 @@ final class DimensionWrapper extends org.apache.sis.internal.netcdf.Dimension {
      * dimensions that are private to a variable, because those dimensions may be unnamed.
      * Consequently value -1 should be used only for shared dimensions.
      *
-     * <a href="https://github.com/Unidata/netcdf-java/issues/951">Issue #951 on netcdf-java</a>
+     * @see <a href="https://github.com/Unidata/netcdf-java/issues/951">Issue #951 on netcdf-java</a>
      */
     private final int index;
 
@@ -109,6 +109,17 @@ final class DimensionWrapper extends org.apache.sis.internal.netcdf.Dimension {
     }
 
     /**
+     * Returns a dimension with its index decremented by 1. This method is invoked for trailing dimensions
+     * after a previous dimension has been removed from a list.
+     *
+     * @return a dimension equals to this one but with its list index (if any) decremented.
+     */
+    @Override
+    protected org.apache.sis.internal.netcdf.Dimension decrementIndex() {
+        return new DimensionWrapper(netcdf, index - 1);
+    }
+
+    /**
      * Returns {@code true} if the given object represents the same dimension than this object.
      * If the dimension is shared, then it has a unique name and {@link Dimension#equals(Object)}
      * can distinguish dimensions based on their name. But if the dimension is private to a variable,

[sis] 02/05: Add a safety check against `NullPointerException` if a concatenated operation contains a defining operation.

Posted by de...@apache.org.
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 c560a142446309987d39b3c8a740953ca8ddd15e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Jan 10 18:12:30 2022 +0100

    Add a safety check against `NullPointerException` if a concatenated operation contains a defining operation.
---
 .../java/org/apache/sis/internal/referencing/Resources.java  |  5 +++++
 .../org/apache/sis/internal/referencing/Resources.properties |  1 +
 .../apache/sis/internal/referencing/Resources_fr.properties  |  1 +
 .../referencing/operation/DefaultConcatenatedOperation.java  | 12 +++++++++---
 4 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
index 5d523d6..b3e451d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
@@ -496,6 +496,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short NotFormalProjectionParameter_1 = 60;
 
         /**
+         * Operation “{1}” of class ‘{0}’ has no mathematical transform.
+         */
+        public static final short OperationHasNoTransform_2 = 101;
+
+        /**
          * No parameter named “{1}” has been found in “{0}”.
          */
         public static final short ParameterNotFound_2 = 61;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
index ad2e8c7..59a3282 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
@@ -111,6 +111,7 @@ NotASkewSymmetricMatrix           = Matrix is not skew-symmetric.
 NoSuchAuthorityCode_3             = No \u2018{1}\u2019 object found for code \u201c{2}\u201d in the \u201c{0}\u201d geodetic dataset.
 NoSuchAuthorityCodeInSubset_4     = No \u2018{1}\u2019 object found for code \u201c{2}\u201d. However only a subset of the {0} geodetic dataset has been queried. See {3} for instruction about how to install the full {0} database.
 NoSuchOperationMethod_1           = No operation method found for name or identifier \u201c{0}\u201d.
+OperationHasNoTransform_2         = Operation \u201c{1}\u201d of class \u2018{0}\u2019 has no mathematical transform.
 ParameterNotFound_2               = No parameter named \u201c{1}\u201d has been found in \u201c{0}\u201d.
 PointsAreNotOnRegularGrid         = Points are not on a regular grid.
 RecursiveCreateCallForCode_2      = Recursive call while creating an object of type \u2018{0}\u2019 for code \u201c{1}\u201d.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
index b647712..6ef6d37 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
@@ -116,6 +116,7 @@ NotASkewSymmetricMatrix           = La matrice n\u2019est pas antisym\u00e9triqu
 NoSuchAuthorityCode_3             = Aucun objet de type \u2018{1}\u2019 n\u2019a \u00e9t\u00e9 trouv\u00e9 pour le code \u00ab\u202f{2}\u202f\u00bb dans la base de donn\u00e9es g\u00e9od\u00e9siques \u00ab\u202f{0}\u202f\u00bb.
 NoSuchAuthorityCodeInSubset_4     = Aucun objet de type \u2018{1}\u2019 n\u2019a \u00e9t\u00e9 trouv\u00e9 pour le code \u00ab\u202f{2}\u202f\u00bb. Toutefois seul un sous-ensemble de la base de donn\u00e9es g\u00e9od\u00e9siques {0} a \u00e9t\u00e9 interrog\u00e9. Voir {3} pour des instructions d\u2019installation de la base de donn\u00e9es {0} compl\u00e8te.
 NoSuchOperationMethod_1           = Aucune m\u00e9thode n\u2019a \u00e9t\u00e9 trouv\u00e9e pour le nom ou l\u2019identifiant \u00ab\u202f{0}\u202f\u00bb.
+OperationHasNoTransform_2         = L\u2019op\u00e9ration \u00ab\u202f{1}\u202f\u00bb de classe \u2018{0}\u2019 n\u2019a pas de transformation math\u00e9matique.
 ParameterNotFound_2               = Aucun param\u00e8tre nomm\u00e9 \u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9 dans \u00ab\u202f{0}\u202f\u00bb.
 PointsAreNotOnRegularGrid         = Les points ne sont pas sur une grille r\u00e9guli\u00e8re.
 RecursiveCreateCallForCode_2      = Appels r\u00e9cursifs lors de la cr\u00e9ation d\u2019un objet de type \u2018{0}\u2019 pour le code \u00ab\u202f{1}\u202f\u00bb.
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 ee26406..3899107 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
@@ -35,6 +35,7 @@ import org.opengis.referencing.operation.Transformation;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
+import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.util.ComparisonMode;
@@ -53,7 +54,7 @@ import static org.apache.sis.util.Utilities.deepEquals;
  * reference system associated with the concatenated operation.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.2
  * @since   0.6
  * @module
  */
@@ -105,7 +106,7 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp
      * @param  mtFactory   the math transform factory to use for math transforms concatenation.
      * @throws FactoryException if the factory can not concatenate the math transforms.
      */
-    public DefaultConcatenatedOperation(final Map<String,?> properties, CoordinateOperation[] operations,
+    public DefaultConcatenatedOperation(final Map<String,?> properties, final CoordinateOperation[] operations,
             final MathTransformFactory mtFactory) throws FactoryException
     {
         super(properties);
@@ -226,7 +227,12 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp
              * however that we traverse nested concatenated operations unconditionally at least for checking
              * its consistency.
              */
-            MathTransform step = op.getMathTransform();
+            final MathTransform step = op.getMathTransform();
+            if (step == null) {
+                // May happen if the operation is a defining operation.
+                throw new IllegalArgumentException(Resources.format(
+                        Resources.Keys.OperationHasNoTransform_2, op.getClass(), op.getName()));
+            }
             if (op instanceof ConcatenatedOperation) {
                 final List<? extends CoordinateOperation> children = ((ConcatenatedOperation) op).getOperations();
                 @SuppressWarnings("SuspiciousToArrayCall")