sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Replace the null value in Category.range by a range containing NaN values. The intent is to remove the Category.minimum/maximum fields in a future commit.
Date Mon, 21 Jan 2019 19:07:37 GMT
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 19a871140542ea8cbdfa1db53913d67255a1f8ba
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jan 21 19:33:07 2019 +0100

    Replace the null value in Category.range by a range containing NaN values. The intent
is to remove the Category.minimum/maximum fields in a future commit.
---
 .../java/org/apache/sis/coverage/Category.java     | 78 +++++++++++-----------
 .../java/org/apache/sis/coverage/CategoryList.java | 29 ++++----
 .../org/apache/sis/coverage/ConvertedCategory.java |  2 +-
 .../org/apache/sis/coverage/SampleDimension.java   | 31 +++------
 .../org/apache/sis/coverage/SampleRangeFormat.java |  9 ++-
 .../org/apache/sis/coverage/CategoryListTest.java  |  2 +-
 .../java/org/apache/sis/coverage/CategoryTest.java | 13 +++-
 .../java/org/apache/sis/measure/NumberRange.java   | 22 +++++-
 .../main/java/org/apache/sis/measure/Range.java    |  5 +-
 .../apache/sis/storage/netcdf/GridResource.java    |  6 +-
 10 files changed, 104 insertions(+), 93 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
index 7f94c5b..5b81052 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
@@ -118,19 +118,17 @@ public class Category implements Serializable {
     final double minimum, maximum;
 
     /**
-     * The [{@linkplain #minimum} … {@linkplain #maximum}] range of values, or {@code null}
if that range would
-     * contain {@link Float#NaN} bounds. This is partially redundant with the minimum and
maximum fields, except
-     * for the following differences:
+     * The [minimum … maximum] range of values in this category (never {@code null}). Notes:
      *
      * <ul>
-     *   <li>This field is {@code null} if the minimum and maximum values are NaN (converted
qualitative category).</li>
+     *   <li>The minimum and maximum values may be one of the {@linkplain Float#isNaN()
NaN} values (see below).</li>
      *   <li>The value type may be different than {@link Double} (typically {@link
Integer}).</li>
      *   <li>The bounds may be exclusive instead than inclusive.</li>
      *   <li>The range may be an instance of {@link MeasurementRange} if the {@link
#toConverse} is identity
      *       and the units of measurement are known.</li>
      * </ul>
      *
-     * The range is null if this category is a qualitative category converted to real values.
+     * The range may be {@code NaN} if this category is a qualitative category converted
to real values.
      * Those categories are characterized by two apparently contradictory properties,
      * and are implemented using {@link Float#NaN} values:
      *
@@ -258,7 +256,7 @@ public class Category implements Serializable {
             }
         }
         if (isNaN) {
-            range      = null;
+            range      = samples;
             converse   = this;
             toConverse = identity();
         } else try {
@@ -345,7 +343,12 @@ public class Category implements Serializable {
         if (isQuantitative) {
             range = new ConvertedRange(extremums, minIncluded, maxIncluded, units);
         } else {
-            range = null;
+            final float min = (float) minimum;
+            if (Double.doubleToRawLongBits(minimum) == Double.doubleToRawLongBits(min)) {
+                range = NumberRange.create(Float.class, min);
+            } else {
+                range = NumberRange.create(Double.class, minimum);
+            }
         }
     }
 
@@ -368,6 +371,15 @@ public class Category implements Serializable {
     }
 
     /**
+     * Returns {@code true} if this category is a qualitative category that has been converted
to "real values".
+     * In such case, the real values are {@link Float#isNaN()} numbers. If {@code false},
then this category is
+     * either a quantitative category or a qualitative category that has not been converted
to "real values".
+     */
+    final boolean isConvertedQualitative() {
+        return Double.isNaN(range.getMinDouble());
+    }
+
+    /**
      * Returns {@code true} if this category is quantitative. A quantitative category has
a
      * {@linkplain #getTransferFunction() transfer function} mapping sample values to values
      * in some units of measurement. By contrast, a qualitative category maps sample values
@@ -378,7 +390,7 @@ public class Category implements Serializable {
      *         {@code false} if this category is qualitative.
      */
     public boolean isQuantitative() {
-        return converted().range != null;
+        return !converted().isConvertedQualitative();
     }
 
     /**
@@ -388,39 +400,16 @@ public class Category implements Serializable {
      * are already real values and the range may be an instance of {@link MeasurementRange}
      * (i.e. a number range with units of measurement).
      *
+     * <p>This method never returns {@code null}, but may return an {@linkplain NumberRange#isBounded()
unbounded range}
+     * or a range containing a singleton {@link Double#NaN} value. The {@code NaN} values
happen if this range is derived
+     * from a "no data" value converted to "real value" by the {@linkplain #getTransferFunction()
transfer function}.</p>
+     *
      * @return the range of sample values in this category.
      *
      * @see SampleDimension#getSampleRange()
      */
-    @SuppressWarnings({"unchecked", "rawtypes"})
     public NumberRange<?> getSampleRange() {
-        if (range != null) {
-            return range;
-        }
-        /*
-         * The range can be null only if the minimum and maximum are NaN. This may be the
case if NaN were
-         * given explicitly to the constructor or if this category is an instance of ConvertedCategory
for
-         * qualitative category. In the later case, the NaN are the result of converting
the sample values.
-         * We favor the Float type because values should be NaN produced by MathFunctions.toNanFloat(int).
-         * The minimum and maximum are usually the same value, but not necessarily.
-         */
-        final float min = (float) minimum;
-        final float max = (float) maximum;
-        final Number v1, v2;
-        final Class<?> type;
-        if (Double.doubleToRawLongBits(minimum) == Double.doubleToRawLongBits(min) &&
-            Double.doubleToRawLongBits(maximum) == Double.doubleToRawLongBits(max))
-        {
-            v1 = min;
-            v2 = (Float.floatToRawIntBits(min) == Float.floatToRawIntBits(max)) ? v1 : max;
-            type = Float.class;
-        } else {
-            v1 = minimum;
-            v2 = (Double.doubleToRawLongBits(minimum) == Double.doubleToRawLongBits(maximum))
? v1 : maximum;
-            type = Double.class;
-        }
-        return new NumberRange(type, v1, true, v2, true);
-        // Do not use NumberRange.create(float, …) because it rejects NaN values.
+        return range;
     }
 
     /**
@@ -432,8 +421,13 @@ public class Category implements Serializable {
      * @see SampleDimension#getMeasurementRange()
      */
     public Optional<MeasurementRange<?>> getMeasurementRange() {
-        // A ClassCastException below would be a bug in our constructor.
-        return Optional.ofNullable((MeasurementRange<?>) converted().range);
+        final NumberRange<?> mr = converted().range;
+        if (Double.isNaN(mr.getMinDouble())) {
+            return Optional.empty();
+        } else {
+            // A ClassCastException below would be a bug in our constructor.
+            return Optional.of((MeasurementRange<?>) mr);
+        }
     }
 
     /**
@@ -464,7 +458,11 @@ public class Category implements Serializable {
          * Note: if this method is invoked on "real values category", then we need to return
          * the identity transform instead than 'toConverse'. This is done by ConvertedCategory.
          */
-        return (converse.range != null) ? Optional.of(toConverse) : Optional.empty();
+        if (converse.isConvertedQualitative()) {
+            return Optional.empty();
+        } else {
+            return Optional.of(toConverse);
+        }
     }
 
     /**
@@ -497,7 +495,7 @@ public class Category implements Serializable {
         }
         if (object != null && getClass().equals(object.getClass())) {
             final Category that = (Category) object;
-            return name.equals(that.name) && Objects.equals(range, that.range) &&
+            return name.equals(that.name) && range.equals(that.range) &&
                    Double.doubleToRawLongBits(minimum) == Double.doubleToRawLongBits(that.minimum)
&&
                    Double.doubleToRawLongBits(maximum) == Double.doubleToRawLongBits(that.maximum)
&&
                    toConverse.equals(that.toConverse);
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
index f008311..b9e4e4b 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
@@ -86,7 +86,7 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
      * The category to use if {@link #search(double)} is invoked with a sample value greater
than all ranges in this list.
      * This is usually a reference to the last category to have a range of real values. A
{@code null} value means that no
      * extrapolation should be used. By extension, a {@code null} value also means that {@link
#search(double)} should not
-     * try to find any fallback at all if the requested sample value does not fall in a category
range.
+     * try to find any fallback if the requested sample value does not fall in a category
range.
      *
      * <p>There is no explicit extrapolation field for values less than all ranges
in this list because the extrapolation
      * to use in such case is {@code categories[0]}.</p>
@@ -131,7 +131,8 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
 
     /**
      * Constructs a category list using the specified array of categories.
-     * The {@code categories} array should contain at least one element.
+     * The {@code categories} array should contain at least one element,
+     * otherwise the {@link #EMPTY} constant should be used.
      *
      * @param  categories  the list of categories. May be empty, but can not be null.
      *                     This array is not cloned and is modified in-place.
@@ -172,17 +173,16 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
                                 category.name, category.getRangeLabel()));
                 }
             }
-            final NumberRange<?> extent = category.range;
-            if (extent != null) {
+            if (!category.isConvertedQualitative()) {
                 /*
-                 * Initialize with the union of ranges at index 0 and index i.  In most cases,
it will cover the whole range
-                 * so all future calls to 'range.unionAny(extent)' will be no-op. The 'categories[0].range'
field should not
-                 * be null because categories with null ranges are sorted last (because their
'minimum' field is NaN).
+                 * Initialize with the union of ranges at index 0 and index i.  In most cases,
the result will cover the whole
+                 * range so all future calls to 'range.unionAny(…)' will be no-op.  The
'categories[0].range' field should not
+                 * be NaN because categories with NaN ranges are sorted last.
                  */
                 if (range == null) {
                     range = categories[0].range;
                 }
-                range = range.unionAny(extent);
+                range = range.unionAny(category.range);
             }
         }
         this.range = range;
@@ -203,10 +203,11 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
             boolean hasQuantitative = false;
             final Category[] convertedCategories = new Category[categories.length];
             for (int i=0; i < convertedCategories.length; i++) {
-                final Category category = categories[i];
-                hasConversion   |= (category != category.converse);
-                hasQuantitative |= (category.converse.range != null);
-                convertedCategories[i] = category.converse;
+                final Category category  = categories[i];
+                final Category converted = category.converse;
+                hasConversion   |= (category != converted);
+                hasQuantitative |= !converted.isConvertedQualitative();
+                convertedCategories[i] = converted;
             }
             if (hasQuantitative) {
                 converse = hasConversion ? new CategoryList(convertedCategories, this) :
this;
@@ -457,6 +458,10 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
                     piece.transform(srcPts, srcOff, dstPts, stepOff, count);
                 }
             }
+            /*
+             * If extrapolation may have happened, verify that transformed values are in
expected ranges.
+             * Values out of range will be clamped.
+             */
             if (extrapolation != null) {
                 dstOff = srcOff + srcToDst;
                 final Category converse = category.converse;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedCategory.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedCategory.java
index 00c9007..fcfed80 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedCategory.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedCategory.java
@@ -69,6 +69,6 @@ final class ConvertedCategory extends Category {
      */
     @Override
     public Optional<MathTransform1D> getTransferFunction() {
-        return (range != null) ? Optional.of(identity()) : Optional.empty();
+        return isConvertedQualitative() ? Optional.empty() : Optional.of(identity());
     }
 }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
index 8bf397f..074dc32 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -134,7 +134,7 @@ public class SampleDimension implements Serializable {
      * @param  original  the original sample dimension for packed values.
      * @param  bc        category of the background value in original sample dimension, or
{@code null}.
      */
-    private SampleDimension(final SampleDimension original, Category bc) {
+    private SampleDimension(final SampleDimension original, final Category bc) {
         converse         = original;
         name             = original.name;
         categories       = original.categories.converse;
@@ -143,13 +143,7 @@ public class SampleDimension implements Serializable {
         if (bc == null) {
             background = null;
         } else {
-            bc = bc.converse;
-            final NumberRange<?> range = bc.range;
-            if (range != null) {
-                background = range.getMinValue();
-            } else {
-                background = (float) bc.minimum;
-            }
+            background = bc.converse.range.getMinValue();
         }
     }
 
@@ -263,8 +257,9 @@ public class SampleDimension implements Serializable {
             Class<? extends Number> widestClass = Byte.class;
             int count = 0;
             for (final Category category : categories) {
-                final NumberRange<?> range = category.range;
-                if (range != null && !category.isQuantitative()) {
+                final Category converted = category.converted();
+                if (category != converted && converted.isConvertedQualitative())
{
+                    final NumberRange<?> range = category.range;
                     if (!range.isBounded()) {
                         throw new IllegalStateException(Resources.format(Resources.Keys.CanNotEnumerateValuesInRange_1,
range));
                     }
@@ -950,20 +945,12 @@ public class SampleDimension implements Serializable {
         }
 
         /**
-         * Returns {@code true} if the given range intersects the range of a previously added
category.
-         * This method can be invoked before to add a new category for checking if it would
cause a range collision.
+         * Returns an unmodifiable view of the list of categories added so far.
          *
-         * @param  minimum  minimal value of the range to test, inclusive.
-         * @param  maximum  maximal value of the range to test, inclusive.
-         * @return whether the given range intersects at least one previously added range.
+         * @return an unmodifiable view of the current category list.
          */
-        public boolean rangeCollides(final double minimum, final double maximum) {
-            for (final Category category : categories) {
-                if (maximum >= category.minimum && minimum <= category.maximum)
{
-                    return true;
-                }
-            }
-            return false;
+        public List<Category> categories() {
+            return Collections.unmodifiableList(categories);
         }
 
         /**
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
index 4a1e1df..ccf4712 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
@@ -160,9 +160,6 @@ final class SampleRangeFormat extends RangeFormat {
      * @return the range to write, or {@code null} if the given {@code range} argument was
null.
      */
     private String formatMeasure(final Range<?> range) {
-        if (range == null) {
-            return null;
-        }
         final NumberFormat nf = (NumberFormat) elementFormat;
         final int min = nf.getMinimumFractionDigits();
         final int max = nf.getMaximumFractionDigits();
@@ -226,9 +223,11 @@ final class SampleRangeFormat extends RangeFormat {
                  */
                 if (hasQuantitative) {
                     final Category converted = category.converted();
-                    String text = formatMeasure(converted.range);               // Example:
[6.0 … 25.0)°C
-                    if (text == null) {
+                    final String text;
+                    if (converted.isConvertedQualitative()) {
                         text = String.valueOf(converted.getRangeLabel());       // Example:
NaN #0
+                    } else {
+                        text = formatMeasure(converted.getSampleRange());       // Example:
[6.0 … 25.0)°C
                     }
                     table.append(text);
                     table.nextColumn();
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
index 211a53a..2e8bc25 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
@@ -63,7 +63,7 @@ public final strictfp class CategoryListTest extends TestCase {
      */
     private static void assertNotConverted(final CategoryList categories) {
         for (final Category c : categories) {
-            assertNotNull(c.range);
+            assertFalse("isNaN", Double.isNaN(c.range.getMinDouble()));
             assertFalse(c.range instanceof ConvertedRange);
         }
     }
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
index 11b659b..b0056f3 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
@@ -44,6 +44,15 @@ public final strictfp class CategoryTest extends TestCase {
     static final double EPS = 1E-9;
 
     /**
+     * Asserts that the given range contains NaN values.
+     */
+    private static void assertNaN(final String message, final NumberRange<?> range)
{
+        final double value = range.getMinDouble();
+        assertTrue(message, Double.isNaN(value));
+        assertEquals(message, Double.doubleToRawLongBits(value), Double.doubleToRawLongBits(range.getMaxDouble()));
+    }
+
+    /**
      * Checks if a {@link Comparable} is a number identical to the supplied integer value.
      */
     private static void assertBoundEquals(final String message, final int expected, final
Comparable<?> actual) {
@@ -107,7 +116,7 @@ public final strictfp class CategoryTest extends TestCase {
             assertEquals ("name",               "Random", String.valueOf(converse.getName()));
             assertTrue   ("minimum",                      Double.isNaN(converse.minimum));
             assertTrue   ("maximum",                      Double.isNaN(converse.maximum));
-            assertNull   ("range",                        converse.range);
+            assertNaN    ("range",                        converse.range);
             assertNotNull("sampleRange",                  converse.getSampleRange());
             assertFalse  ("measurementRange",             category.getMeasurementRange().isPresent());
             assertFalse  ("toConverse.isIdentity",        converse.toConverse.isIdentity());
@@ -231,7 +240,7 @@ public final strictfp class CategoryTest extends TestCase {
         assertEquals("name",           "NaN",      String.valueOf(category.getName()));
         assertEquals("minimum",        Double.NaN, category.minimum, STRICT);
         assertEquals("maximum",        Double.NaN, category.maximum, STRICT);
-        assertNull  ("sampleRange",                category.range);
+        assertNaN   ("sampleRange",                category.range);
         assertEquals("range.minValue", Float.NaN,  range.getMinValue());
         assertEquals("range.maxValue", Float.NaN,  range.getMaxValue());
         assertFalse ("measurementRange",           category.getMeasurementRange().isPresent());
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
index 7091dd9..1aad501 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
@@ -122,6 +122,22 @@ public class NumberRange<E extends Number & Comparable<? super
E>> extends Range
     }
 
     /**
+     * Constructs a range containing a single value of the given type.
+     * The given value is used as the minimum and maximum values, inclusive.
+     *
+     * @param  <N>     compile-time value of {@code type}.
+     * @param  type    the element type, usually one of {@link Byte}, {@link Short},
+     *                 {@link Integer}, {@link Long}, {@link Float} or {@link Double}.
+     * @param  value   the value, or {@code null} for creating an unbounded range.
+     * @return a range containing the given value as its inclusive minimum and maximum.
+     *
+     * @since 1.0
+     */
+    public static <N extends Number & Comparable<? super N>> NumberRange<N>
create(final Class<N> type, final N value) {
+        return unique(new NumberRange<>(type, value, true, value, true));
+    }
+
+    /**
      * Constructs a range of {@code byte} values.
      * This method may return a shared instance, at implementation choice.
      *
@@ -592,12 +608,12 @@ public class NumberRange<E extends Number & Comparable<? super
E>> extends Range
     }
 
     /**
-     * Returns {@code true} if the supplied range is fully contained within this range.
+     * Returns {@code true} if the supplied range intersects this range.
      * This method converts {@code this} or the given argument to the widest numeric type,
      * then delegates to {@link #intersects(Range)}.
      *
-     * @param  range  the range to check for inclusion in this range.
-     * @return {@code true} if the given range is included in this range.
+     * @param  range  the range to check for intersection with this range.
+     * @return {@code true} if the given range intersects this range.
      * @throws IllegalArgumentException if the given range can not be converted to a valid
type
      *         through widening conversion, or if the units of measurement are not convertible.
      */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
index bb653dd..32ae059 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
@@ -587,10 +587,11 @@ public class Range<E extends Comparable<? super E>> implements
CheckedContainer<
      * <ul>
      *   <li>are both {@linkplain #isEmpty() empty}, or</li>
      *   <li>have equal {@linkplain #getMinValue() minimum} and {@linkplain #getMaxValue()
maximum} values
-     *       with equal inclusive/exclusive flags.</li>
+     *       with equal inclusive/exclusive flags. Note that numbers in {@link Float} or
{@link Double}
+     *       wrappers consider all {@code NaN} values as equal.</li>
      * </ul>
      *
-     * Note that subclasses may add other requirements, for example on units of measurement.
+     * Subclasses may add other requirements, for example on units of measurement.
      *
      * @param  object  the object to compare with this range for equality.
      * @return {@code true} if the given object is equal to this range.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
index 53c410a..06c1d7c 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
@@ -327,11 +327,7 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
             if (ordinal >= 0) {
                 n = MathFunctions.toNanFloat(ordinal++);        // Must be consistent with
Variable.replaceNaN(Object).
             } else {
-                n = entry.getKey();
-                final double fp = n.doubleValue();
-                if (builder.rangeCollides(fp, fp)) {
-                    continue;
-                }
+                n = entry.getKey();                             // Should be real number,
made unique by the HashMap.
             }
             final int role = entry.getValue();          // Bit 0 set (value 1) = pad value,
bit 1 set = missing value.
             final int i = (role == 1) ? 1 : 0;          // i=1 if role is only pad value,
i=0 otherwise.


Mime
View raw message