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 359caffd38a46c91bcf0273555e7763b27584bee Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Feb 26 16:40:24 2022 +0100 Values returned by `ArrayVector.get(int)` need to be instance of more predictable classes. This is needed by the netCDF reader of features, which expects instances of specific types. --- .../org/apache/sis/feature/AbstractFeature.java | 2 +- .../main/java/org/apache/sis/math/ArrayVector.java | 24 +++----- .../src/main/java/org/apache/sis/math/Vector.java | 12 +++- .../test/java/org/apache/sis/math/VectorTest.java | 69 +++++++++++++--------- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java index dcc2fd6..3afed12 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java +++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java @@ -510,7 +510,7 @@ public abstract class AbstractFeature implements Feature, Serializable { } while ((element = it.next()) == null || base.isInstance(element)); // Found an illegal value. Exeption is thrown below. } - throw new ClassCastException(illegalValueClass(pt, base, element)); // 'element' can not be null here. + throw new ClassCastException(illegalValueClass(pt, base, element)); // `element` can not be null here. } } ((Attribute) attribute).setValue(value); diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java b/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java index 25c2aea..1b5c65a 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java +++ b/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java @@ -42,7 +42,7 @@ import org.apache.sis.measure.NumberRange; * so changes in the underlying array is reflected in this vector and vice-versa. * * @author Martin Desruisseaux (MPO, Geomatys) - * @version 1.1 + * @version 1.2 * @since 0.8 * @module */ @@ -1039,11 +1039,9 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Integer.SIZE)); } - /** Uses a larger type if the value exceed integer capacity. */ + /** Unconditionally uses a larger type since the value may exceed integer capacity. */ @Override public Number get(int index) { - final int value = super.intValue(index); - if (value >= 0) return value; - return Integer.toUnsignedLong(value); + return longValue(index); } /** Verifies that the given value can be stored as an unsigned integer. */ @@ -1095,11 +1093,9 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Short.SIZE)); } - /** Uses a larger type if the value exceed short integer capacity. */ + /** Unconditionally uses a larger type since the value may exceed short capacity. */ @Override public Number get(int index) { - final short value = super.shortValue(index); - if (value >= 0) return value; - return Short.toUnsignedInt(value); + return intValue(index); } /** Verifies that the given value can be stored as an unsigned integer. */ @@ -1143,8 +1139,8 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo @Override public boolean isUnsigned() {return true;} @Override public double doubleValue(int index) {return intValue(index);} @Override public float floatValue(int index) {return intValue(index);} - @Override public long longValue(int index) {return Byte.toUnsignedLong (super.byteValue(index));} - @Override public int intValue(int index) {return Byte.toUnsignedInt (super.byteValue(index));} + @Override public long longValue(int index) {return Byte.toUnsignedLong(super.byteValue(index));} + @Override public int intValue(int index) {return Byte.toUnsignedInt (super.byteValue(index));} @Override public short shortValue(int index) {return (short) intValue(index);} @Override public byte byteValue(int index) { final byte value = super.byteValue(index); @@ -1152,11 +1148,9 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Byte.SIZE)); } - /** Uses a larger type if the value exceed integer capacity. */ + /** Unconditionally uses a larger type since the value may exceed byte capacity. */ @Override public Number get(int index) { - final byte value = super.byteValue(index); - if (value >= 0) return value; - return (short) Byte.toUnsignedInt(value); + return shortValue(index); } /** Verifies that the given value can be stored as an unsigned integer. */ diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java index 97f4b97..90d0cca 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java +++ b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java @@ -248,7 +248,7 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces * * <ul> * <li>If this vector {@linkplain #isUnsigned() is unsigned}, then the values returned by {@code get(int)} - * may be instances of a type wider than the type used by this vector for storing the values.</li> + * will be instances of a type wider than the type used by this vector for storing the values.</li> * <li>If this vector has been {@linkplain #createForDecimal(float[]) created for decimal numbers}, * then the values returned by {@code get(int)} will use double-precision even if this vector * stores the values as single-precision floating point numbers.</li> @@ -518,8 +518,14 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces * * <div class="note"><b>Example:</b> * if {@link #getElementType()} returns {@code Byte.class} but {@link #isUnsigned()} returns {@code true}, - * then this method may return instances of {@link Short} since that type is the smallest Java primitive - * type capable to hold byte values in the [0 … 255] range.</div> + * then this method will rather return instances of {@link Short} because that type is the smallest Java + * primitive type capable to hold byte values in the [0 … 255] range. But the elements are still stored + * internally as {@code byte}, and the vector can not accept values outside the [0 … 255] range even if + * they are valid {@link Short} values.</div> + * + * The class of returned objects should be stable. For example this method should not use different types + * for different range of values. This stability is recommended but not guaranteed because {@code Vector} + * can also wrap arbitrary {@code Number[]} arrays. * * @param index the index in the [0 … {@linkplain #size() size}-1] range. * @return the value at the given index (may be {@code null}). diff --git a/core/sis-utility/src/test/java/org/apache/sis/math/VectorTest.java b/core/sis-utility/src/test/java/org/apache/sis/math/VectorTest.java index f2ed27c..c27581b 100644 --- a/core/sis-utility/src/test/java/org/apache/sis/math/VectorTest.java +++ b/core/sis-utility/src/test/java/org/apache/sis/math/VectorTest.java @@ -28,7 +28,7 @@ import static org.opengis.test.Assert.*; * Tests the {@link Vector} class. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * @since 0.8 * @module */ @@ -69,26 +69,31 @@ public final strictfp class VectorTest extends TestCase { * We use the {@code short} type for this test. */ @Test + @SuppressWarnings("UnnecessaryBoxing") public void testShortArray() { final short[] array = new short[400]; for (int i=0; i<array.length; i++) { - array[i] = (short) ((i + 100) * 10); + array[i] = (short) ((i - 20) * 10); } vector = Vector.create(array, false); assertTrue(vector instanceof ArrayVector); assertSame(vector, Vector.create(vector, false)); assertEquals(array.length, vector.size()); assertEquals(Short.class, vector.getElementType()); - - // Verifies element values. + /* + * Verify element values. The wrapper class shall be `Short`. + */ for (int i=0; i<array.length; i++) { - assertEquals(array[i], vector.shortValue (i)); - assertEquals(array[i], vector.intValue (i)); - assertEquals(array[i], vector.floatValue (i), 0f); - assertEquals(array[i], vector.doubleValue(i), STRICT); + final short expected = array[i]; + assertEquals(expected, vector.shortValue (i)); + assertEquals(expected, vector.intValue (i)); + assertEquals(expected, vector.floatValue (i), 0f); + assertEquals(expected, vector.doubleValue(i), STRICT); + assertEquals(Short.valueOf(expected), vector.get(i)); } - - // Tests exception. + /* + * Test exception for invalid index and for invalid narrowing cast. + */ try { vector.floatValue(array.length); fail("Expected an IndexOutOfBoundsException"); @@ -97,15 +102,15 @@ public final strictfp class VectorTest extends TestCase { } try { vector.byteValue(0); - fail("Expected a ArithmeticException"); + fail("Expected an ArithmeticException"); } catch (ArithmeticException e) { // This is the expected exception. final String message = e.getMessage(); - assertTrue(message, message.contains("1000")); assertTrue(message, message.contains("byte")); } - - // Tests subvector in the range [100:2:298]. + /* + * Test subvector in the range [100:2:298]. + */ vector = vector.subSampling(100, 2, 100); assertEquals(100, vector.size()); for (int i=0; i<100; i++) { @@ -117,8 +122,10 @@ public final strictfp class VectorTest extends TestCase { } catch (IndexOutOfBoundsException e) { // This is the expected exception. } - - // Tests subvector at specific indexes. + /* + * Test subvector at specific indices. The indices of picked values below and indices + * in the sub-vector tested above. They may to the original array by `index*2 + 100`. + */ vector = vector.pick(10, 20, 25); assertEquals(3, vector.size()); assertEquals(array[120], vector.shortValue(0)); @@ -130,6 +137,7 @@ public final strictfp class VectorTest extends TestCase { * Tests {@link ArrayVector} backed by an array of primitive type handled as unsigned values. */ @Test + @SuppressWarnings("UnnecessaryBoxing") public void testUnsignedByteArray() { final byte[] array = new byte[100]; for (int i=0; i<array.length; i++) { @@ -140,27 +148,34 @@ public final strictfp class VectorTest extends TestCase { assertSame(vector, Vector.create(vector, true)); assertEquals(array.length, vector.size()); assertEquals(Byte.class, vector.getElementType()); - - // Verifies element values. + /* + * Verify element values. Bytes shall be casted to shorts in order to handle unsigned values. + * The widening casts should be unconditional, even for positive values that could fit in a byte, + * because some codes using `Vector` expect a stable type (for example `NetcdfStore` which copies + * vector values into feature properties). + */ for (int i=0; i<array.length; i++) { final int expected = Byte.toUnsignedInt(array[i]); assertEquals(expected, vector.shortValue (i)); assertEquals(expected, vector.intValue (i)); assertEquals(expected, vector.floatValue (i), 0f); assertEquals(expected, vector.doubleValue(i), STRICT); + assertEquals(Short.valueOf((short) expected), vector.get(i)); // See above comment. } - - // Tests exception. - assertEquals(106, vector.byteValue(50)); + /* + * Test exception for invalid narrowing cast. + */ + assertEquals(106, vector.byteValue (50)); assertEquals(146, vector.shortValue(70)); try { vector.byteValue(70); - fail("Expected a ArithmeticException"); + fail("Expected an ArithmeticException"); } catch (ArithmeticException e) { // This is the expected exception. } - - // Test writing. + /* + * Test writing. + */ vector.set(70, (short) 200); assertEquals(200, vector.shortValue(70)); } @@ -203,7 +218,7 @@ public final strictfp class VectorTest extends TestCase { assertEquals(array.length, vector.size()); assertEquals(Double.class, vector.getElementType()); /* - * Tests element values. + * Test element values. */ for (int i=0; i<array.length; i++) { assertEquals(array[i], vector.floatValue (i), 0f); @@ -251,8 +266,8 @@ public final strictfp class VectorTest extends TestCase { assertSame("Should be able to restitute the original vector.", v1, v3.subList( 0, 40)); assertSame("Should be able to restitute the original vector.", v2, v3.subList(40, 60)); /* - * Tests concatenation of views at fixed indices. Should be - * implemented as the concatenation of the indices arrays when possible. + * Test concatenation of views at fixed indices. + * Should be implemented as the concatenation of the indices arrays when possible. */ final Vector expected = v3.pick(10, 25, 30, 0, 35, 39); v2 = v1.pick( 0, 35, 39);