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 2018/06/18 21:02:39 UTC

[sis] 17/33: More robust parsing of GeoTIFF tags: when expecting a single value, accept an array if all values are the same. Also accept -1 as a code for "missing units" but verify if the unit is really missing.

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 e5688f446115ade33bcd8c3ba5699f4fb0bb712d
Author: Martin Desruisseaux <de...@apache.org>
AuthorDate: Sat Jun 2 11:31:17 2018 +0000

    More robust parsing of GeoTIFF tags: when expecting a single value, accept an array if all values are the same. Also accept -1 as a code for "missing units" but verify if the unit is really missing.
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sis/branches/JDK8@1832731 13f79535-47bb-0310-9956-ffa450edef68
---
 .../org/apache/sis/storage/geotiff/CRSBuilder.java |  13 +-
 .../org/apache/sis/storage/geotiff/GeoCodes.java   |   8 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |  14 +-
 .../java/org/apache/sis/storage/geotiff/Type.java  | 259 ++++++++++++---------
 .../org/apache/sis/storage/geotiff/TypeTest.java   |  23 +-
 5 files changed, 194 insertions(+), 123 deletions(-)

diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
index 34c739e..5cffea1 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
@@ -132,7 +132,7 @@ import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see GeoKeys
  *
@@ -457,7 +457,7 @@ final class CRSBuilder {
      */
     private double getMandatoryDouble(final short key) {
         final double value = getAsDouble(key);
-        if (!Double.isNaN(value)) {
+        if (Double.isFinite(value)) {
             return value;
         }
         alreadyReported = true;
@@ -869,6 +869,15 @@ final class CRSBuilder {
                 if (scaleKey == 0) return defaultValue;
                 return defaultValue.getSystemUnit().multiply(getMandatoryDouble(scaleKey));
             }
+            case GeoCodes.missing & 0xFFFF: {
+                if (scaleKey != 0) {
+                    final double scale = getAsDouble(scaleKey);
+                    if (Double.isFinite(scale)) {
+                        return defaultValue.getSystemUnit().multiply(scale);
+                    }
+                }
+                return defaultValue;
+            }
             default: {
                 /*
                  * Unit defined by an EPSG code. In principle we should just use the EPSG code.
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java
index 7f23aed..c2d0fcb 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java
@@ -23,7 +23,7 @@ package org.apache.sis.storage.geotiff;
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -44,6 +44,12 @@ final class GeoCodes {
      */
     public static final short userDefined = 32767;
 
+    /**
+     * An alternative code for {@link #undefined} found in some GeoTIFF file.
+     * This is not a standard value. This is used only in some methods implemented defensively.
+     */
+    static final short missing = -1;
+
     /*
      * 6.3.1.1 Model Type Codes
      *
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 25245d2..be28f26 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -52,7 +52,7 @@ import org.apache.sis.measure.Units;
  * @author  Johann Sorel (Geomatys)
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a>
  *
@@ -288,6 +288,12 @@ final class ImageFileDirectory {
     private Vector minValues, maxValues;
 
     /**
+     * {@code true} if {@link #minValues} and {@link #maxValues} have been explicitly specified
+     * in the TIFF file, or {@code false} if they have been inferred from {@link #bitsPerSample}.
+     */
+    private boolean isMinSpecified, isMaxSpecified;
+
+    /**
      * The number of pixels per {@link #resolutionUnit} in the {@link #imageWidth} and the {@link #imageHeight}
      * directions, or {@link Double#NaN} is unspecified. Since ISO 19115 does not have separated resolution fields
      * for image width and height, Apache SIS stores only the maximal value.
@@ -634,6 +640,7 @@ final class ImageFileDirectory {
             case Tags.MinSampleValue:
             case Tags.SMinSampleValue: {
                 minValues = extremum(minValues, type.readVector(input(), count), false);
+                isMinSpecified = true;
                 break;
             }
             /*
@@ -644,6 +651,7 @@ final class ImageFileDirectory {
             case Tags.MaxSampleValue:
             case Tags.SMaxSampleValue: {
                 maxValues = extremum(maxValues, type.readVector(input(), count), true);
+                isMaxSpecified = true;
                 break;
             }
 
@@ -1180,8 +1188,8 @@ final class ImageFileDirectory {
         for (int band = 0; band < samplesPerPixel;) {
             metadata.newSampleDimension();
             metadata.setBitPerSample(bitsPerSample);
-            if (minValues != null) metadata.addMinimumSampleValue(minValues.doubleValue(Math.min(band, minValues.size()-1)));
-            if (maxValues != null) metadata.addMaximumSampleValue(maxValues.doubleValue(Math.min(band, maxValues.size()-1)));
+            if (isMinSpecified) metadata.addMinimumSampleValue(minValues.doubleValue(Math.min(band, minValues.size()-1)));
+            if (isMaxSpecified) metadata.addMaximumSampleValue(maxValues.doubleValue(Math.min(band, maxValues.size()-1)));
             metadata.setBandIdentifier(++band);
         }
         /*
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
index 64dce2e..ef59ee0 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
@@ -36,7 +36,7 @@ import org.apache.sis.util.resources.Errors;
  * This enumeration rather match the Java primitive type names.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -48,7 +48,11 @@ enum Type {
      *   <li>TIFF code: 7</li>
      * </ul>
      */
-    UNDEFINED(7, Byte.BYTES, false),
+    UNDEFINED(7, Byte.BYTES, false) {
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            throw new UnsupportedOperationException(name());
+        }
+    },
 
     /**
      * An 8-bits signed (twos-complement) integer.
@@ -58,12 +62,15 @@ enum Type {
      * </ul>
      */
     BYTE(6, Byte.BYTES, false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readByte();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readByte();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readByte(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readBytes(count);
         }
     },
@@ -76,12 +83,15 @@ enum Type {
      * </ul>
      */
     UBYTE(1, Byte.BYTES, true) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readUnsignedByte();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readUnsignedByte();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readUnsignedByte(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readBytes(count);
         }
     },
@@ -94,12 +104,15 @@ enum Type {
      * </ul>
      */
     SHORT(8, Short.BYTES, false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readShort();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readShort();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readShort(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readShorts(count);
         }
     },
@@ -112,12 +125,15 @@ enum Type {
      * </ul>
      */
     USHORT(3, Short.BYTES, true) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readUnsignedShort();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readUnsignedShort();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readUnsignedShort(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readShorts(count);
         }
     },
@@ -130,12 +146,15 @@ enum Type {
      * </ul>
      */
     INT(9, Integer.BYTES, false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readInt();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readInt();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readInt(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readInts(count);
         }
     },
@@ -148,12 +167,15 @@ enum Type {
      * </ul>
      */
     UINT(4, Integer.BYTES, true) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readUnsignedInt();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readUnsignedInt();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readUnsignedInt(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readInts(count);
         }
     },
@@ -165,12 +187,15 @@ enum Type {
      * </ul>
      */
     LONG(17, Long.BYTES, false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readLong();
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final long value = input.readLong();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readLong(), count);
+            }
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readLongs(count);
         }
     },
@@ -182,21 +207,22 @@ enum Type {
      * </ul>
      */
     ULONG(16, Long.BYTES, true) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
             final long value = input.readLong();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readLong(), count);
+            }
             if (value >= 0) {
                 return value;
             }
             throw new ArithmeticException(canNotConvert(Long.toUnsignedString(value)));
         }
 
-        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return Numerics.toUnsignedDouble(input.readLong());
+        @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
+            return Numerics.toUnsignedDouble(readLong(input, count));
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readLongs(count);
         }
     },
@@ -209,9 +235,16 @@ enum Type {
      * </ul>
      */
     FLOAT(11, Float.BYTES, false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
+        private float readFloat(final ChannelDataInput input, final long count) throws IOException {
             final float value = input.readFloat();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readFloat(), count);
+            }
+            return value;
+        }
+
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            final float value = readFloat(input, count);
             final long r = (long) value;
             if (r == value) {
                 return r;
@@ -219,12 +252,11 @@ enum Type {
             throw new ArithmeticException(canNotConvert(Float.toString(value)));
         }
 
-        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return DecimalFunctions.floatToDouble(input.readFloat());
+        @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
+            return DecimalFunctions.floatToDouble(readFloat(input, count));
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readFloats(count);
         }
     },
@@ -237,22 +269,15 @@ enum Type {
      * </ul>
      */
     DOUBLE(12, Double.BYTES, false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
+        @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
             final double value = input.readDouble();
-            final long r = (long) value;
-            if (r == value) {
-                return r;
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readDouble(), count);
             }
-            throw new ArithmeticException(canNotConvert(Double.toString(value)));
-        }
-
-        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readDouble();
+            return value;
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return input.readDoubles(count);
         }
     },
@@ -265,26 +290,12 @@ enum Type {
      * </ul>
      */
     RATIONAL(10, (2*Integer.BYTES), false) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            final int numerator   = input.readInt();
-            final int denominator = input.readInt();
-            if ((numerator % denominator) == 0) {
-                return numerator / denominator;
+        @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
+            final double value = input.readInt() / (double) input.readInt();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readInt() / (double) input.readInt(), count);
             }
-            throw new ArithmeticException(canNotConvert(toString(numerator, denominator)));
-        }
-
-        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readInt() / (double) input.readInt();
-        }
-
-        @Override String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
-            ensureSingleton(length);
-            return new String[] {
-                toString(input.readInt(), input.readInt())
-            };
+            return value;
         }
     },
 
@@ -296,26 +307,12 @@ enum Type {
      * </ul>
      */
     URATIONAL(5, (2*Integer.BYTES), true) {
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            final long numerator   = input.readUnsignedInt();
-            final long denominator = input.readUnsignedInt();
-            if ((numerator % denominator) == 0) {
-                return numerator / denominator;
+        @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
+            final double value = input.readUnsignedInt() / (double) input.readUnsignedInt();
+            for (long i=1; i<count; i++) {
+                ensureSingleton(value, input.readUnsignedInt() / (double) input.readUnsignedInt(), count);
             }
-            throw new ArithmeticException(canNotConvert(toString(numerator, denominator)));
-        }
-
-        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
-            ensureSingleton(count);
-            return input.readUnsignedInt() / (double) input.readUnsignedInt();
-        }
-
-        @Override String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
-            ensureSingleton(length);
-            return new String[] {
-                toString(input.readUnsignedInt(), input.readUnsignedInt())
-            };
+            return value;
         }
     },
 
@@ -329,7 +326,7 @@ enum Type {
      * </ul>
      */
     ASCII(2, Byte.BYTES, false) {
-        @Override String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
+        @Override public String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
             final byte[] chars = input.readBytes(Math.toIntExact(length));
             String[] lines = new String[1];                     // We will usually have exactly one string.
             int count = 0, lower = 0;
@@ -344,19 +341,26 @@ enum Type {
             return ArraysExt.resize(lines, count);
         }
 
-        @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
+        private String readString(final ChannelDataInput input, final long count) throws IOException {
             final String[] lines = readString(input, count, StandardCharsets.US_ASCII);
-            ensureSingleton(lines.length);
-            return Long.parseLong(lines[0]);
+            final String value = lines[0];
+            for (int i=1; i<lines.length; i++) {
+                if (!value.equals(lines[i])) {
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
+                }
+            }
+            return value;
         }
 
-        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
-            final String[] lines = readString(input, count, StandardCharsets.US_ASCII);
-            ensureSingleton(lines.length);
-            return Double.parseDouble(lines[0]);
+        @Override public long readLong(final ChannelDataInput input, final long count) throws IOException {
+            return Long.parseLong(readString(input, count));
+        }
+
+        @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
+            return Double.parseDouble(readString(input, count));
         }
 
-        @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
             return readString(input, count, StandardCharsets.US_ASCII);
         }
     };
@@ -416,20 +420,34 @@ enum Type {
 
     /**
      * Invoked by {@code read(…)} method implementations for verifying that the {@code count} argument value is 1.
-     * All read methods expect exactly one value, except the methods of the {@link #ASCII} enumeration value which
-     * are treated differently.
+     * All read methods other than {@code readArray(…)} expect exactly one value, except methods in {@link #ASCII}
+     * enumeration value which are treated differently.
      *
-     * @param  count  the number of values to read.
+     * <p>While exactly one value is expected, we are tolerant to longer arrays provided that all values are the
+     * same. This is seen in practice where a value expected to apply to the image is repeated for each band.</p>
+     *
+     * @param  previous  the previous value.
+     * @param  actual    the actual value.
+     * @param  count     the number of values to read.
      * @throws IllegalArgumentException if {@code count} does not have the expected value.
      */
-    static void ensureSingleton(final long count) {
-        if (count != 1) {
+    static void ensureSingleton(final long previous, final long actual, final long count) {
+        if (previous != actual) {
             // Even if the methods did not expected an array in argument, we are conceptually reading an array.
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
         }
     }
 
     /**
+     * Same as {@link #ensureSingleton(long, long, long)} but with floating-point values.
+     */
+    static void ensureSingleton(final double previous, final double actual, final long count) {
+        if (Double.doubleToLongBits(previous) != Double.doubleToLongBits(actual)) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
+        }
+    }
+
+    /**
      * Formats an error message for a value that can not be converted.
      */
     final String canNotConvert(final String value) {
@@ -449,7 +467,7 @@ enum Type {
      * @throws IllegalArgumentException if the value is not a singleton.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    final short readShort(final ChannelDataInput input, final long count) throws IOException {
+    public final short readShort(final ChannelDataInput input, final long count) throws IOException {
         final long value = readLong(input, count);
         if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
             return (short) value;
@@ -470,7 +488,7 @@ enum Type {
      * @throws IllegalArgumentException if the value is not a singleton.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    final int readInt(final ChannelDataInput input, final long count) throws IOException {
+    public final int readInt(final ChannelDataInput input, final long count) throws IOException {
         final long value = readLong(input, count);
         if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
             return (int) value;
@@ -488,7 +506,7 @@ enum Type {
      * @throws IllegalArgumentException if the value is not a singleton.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    final long readUnsignedLong(final ChannelDataInput input, final long count) throws IOException {
+    public final long readUnsignedLong(final ChannelDataInput input, final long count) throws IOException {
         final long value = readLong(input, count);
         if (value >= 0) {
             return value;
@@ -513,8 +531,14 @@ enum Type {
      * @throws IllegalArgumentException if the value is not a singleton.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    long readLong(ChannelDataInput input, long count) throws IOException {
-        throw new UnsupportedOperationException(name());
+    public long readLong(final ChannelDataInput input, final long count) throws IOException {
+        // All enum MUST override one of 'readLong' or 'readDouble' methods.
+        final double value = readDouble(input, count);
+        final long r = (long) value;
+        if (r == value) {
+            return r;
+        }
+        throw new ArithmeticException(canNotConvert(Double.toString(value)));
     }
 
     /**
@@ -533,7 +557,8 @@ enum Type {
      * @throws IllegalArgumentException if the value is not a singleton.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    double readDouble(ChannelDataInput input, long count) throws IOException {
+    public double readDouble(ChannelDataInput input, long count) throws IOException {
+        // All enum MUST override one of 'readLong' or 'readDouble' methods.
         return readLong(input, count);
     }
 
@@ -548,10 +573,12 @@ enum Type {
      * @throws ArithmeticException if the given length is too large.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
+    public String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
         final String[] s = new String[Math.toIntExact(length)];
         for (int i=0; i<s.length; i++) {
-            s[i] = String.valueOf(readLong(input, 1));
+            final double value = readDouble(input, 1);
+            final long r = (long) value;
+            s[i] = (r == value) ? String.valueOf(r) : String.valueOf(value);
         }
         return s;
     }
@@ -565,7 +592,7 @@ enum Type {
      * @throws IOException if an error occurred while reading the stream.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    Object readArray(ChannelDataInput input, int count) throws IOException {
+    public Object readArray(ChannelDataInput input, int count) throws IOException {
         throw new UnsupportedOperationException(name());
     }
 
@@ -582,7 +609,7 @@ enum Type {
      * @throws NumberFormatException if the value was stored in ASCII and can not be parsed.
      * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
-    final Vector readVector(final ChannelDataInput input, final long count) throws IOException {
+    public final Vector readVector(final ChannelDataInput input, final long count) throws IOException {
         return Vector.create(readArray(input, Math.toIntExact(count)), isUnsigned);
     }
 }
diff --git a/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TypeTest.java b/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TypeTest.java
index afe345f..320169d 100644
--- a/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TypeTest.java
+++ b/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TypeTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.geotiff;
 
+import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -26,12 +27,32 @@ import static org.junit.Assert.*;
  * Tests the {@link Type} enumeration.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
 public final strictfp class TypeTest extends TestCase {
     /**
+     * Verifies that all enumeration values override either {@link Type#readLong(ChannelDataInput, long)}
+     * or {@link Type#readDouble(ChannelDataInput, long)}.Failing to do so may cause stack overflow.
+     *
+     * @throws NoSuchMethodException if a reflective operation failed.
+     */
+    @Test
+    public void testOverride() throws NoSuchMethodException {
+        final Class<?>[] parameters = {
+            ChannelDataInput.class,
+            Long.TYPE
+        };
+        for (final Type type : Type.values()) {
+            final Class<?> c = type.getClass();
+            final boolean readLong   = c.getMethod("readLong",   parameters).getDeclaringClass() == Type.class;
+            final boolean readDouble = c.getMethod("readDouble", parameters).getDeclaringClass() == Type.class;
+            assertFalse(type.name(), readLong & readDouble);
+        }
+    }
+
+    /**
      * Tests {@link Type#valueOf(int)}.
      */
     @Test