sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1803070 [12/20] - in /sis/branches/JDK9: ./ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-build-helper/ core/sis-build-helper/src/main/java/org/apache/sis/internal/book/ core/sis-build-helper/src/main/java/org/apac...
Date Wed, 26 Jul 2017 16:14:14 GMT
Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -176,17 +176,17 @@ public class TableAppender extends Appen
     private String lineSeparator;
 
     /**
-     * The column separator.
+     * The column separator, or an empty string if none.
      */
     private final String columnSeparator;
 
     /**
-     * The left table border.
+     * The left table border, or an empty string if none.
      */
     private final String leftBorder;
 
     /**
-     * The right table border.
+     * The right table border, or an empty string if none.
      */
     private final String rightBorder;
 

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -80,103 +80,77 @@ abstract class ArrayVector<E extends Num
     }
 
     /**
-     * Returns a vector with the same data than this vector but encoded in a more compact way,
-     * or {@code this} if this method can not do better than current {@code Vector} instance.
-     */
-    @Override
-    public final Vector compress(final double tolerance) {
-        final Vector vec = super.compress(tolerance);
-        if (vec == this && !isEmpty()) {
-            if (isInteger()) {
-                /*
-                 * For integer values, verify if we can pack the data into a smaller type.
-                 * We will use a vector backed by IntegerList in order to use only the amount of bits needed,
-                 * unless that amount is exactly the number of bits of a primitive type (8, 16, 32 or 64) in
-                 * which case using one of the specialized class in this ArrayVector is more performant.
-                 */
-                final NumberRange<?> range = range();
-                if (range != null && !range.isEmpty() && range.getMinDouble() >= Long.MIN_VALUE
-                                                      && range.getMaxDouble() <= Long.MAX_VALUE)
-                {
-                    final long min      = range.getMinValue().longValue();
-                    final long max      = range.getMaxValue().longValue();
-                    final long delta    = Math.subtractExact(max, min);
-                    final int  bitCount = Long.SIZE - Long.numberOfLeadingZeros(delta);
-                    if (bitCount != Numbers.primitiveBitCount(getElementType())) {
-                        switch (bitCount) {
-                            case Byte.SIZE: {
-                                final boolean isSigned = (min >= Byte.MIN_VALUE && max <= Byte.MAX_VALUE);
-                                if (isSigned || (min >= 0 && max <= 0xFF)) {
-                                    final byte[] array = new byte[size()];
-                                    for (int i=0; i < array.length; i++) {
-                                        array[i] = (byte) intValue(i);
-                                    }
-                                    return isSigned ? new Bytes(array) : new UnsignedBytes(array);
-                                }
-                                break;
-                            }
-                            case Short.SIZE: {
-                                final boolean isSigned = (min >= Short.MIN_VALUE && max <= Short.MAX_VALUE);
-                                if (isSigned || (min >= 0 && max <= 0xFFFF)) {
-                                    final short[] array = new short[size()];
-                                    for (int i=0; i < array.length; i++) {
-                                        array[i] = (short) intValue(i);
-                                    }
-                                    return isSigned ? new Shorts(array) : new UnsignedShorts(array);
-                                }
-                                break;
-                            }
-                            case Integer.SIZE: {
-                                final boolean isSigned = (min >= Integer.MIN_VALUE && max <= Integer.MAX_VALUE);
-                                if (isSigned || (min >= 0 && max <= 0xFFFFFFFF)) {
-                                    final int[] array = new int[size()];
-                                    for (int i=0; i < array.length; i++) {
-                                        array[i] = (int) longValue(i);
-                                    }
-                                    return isSigned ? new Integers(array) : new UnsignedIntegers(array);
-                                }
-                                break;
-                            }
-                            // The Long.SIZE case should never happen because of the 'bitCount' check before the switch.
-                        }
-                        return new PackedVector(this, min, Math.toIntExact(delta));
-                    }
-                }
-            } else if (!Float.class.equals(getElementType())) {
-                /*
-                 * For floating point types, verify if values are equivalent to 'float' values.
-                 * There is two different ways to pad extra fraction digits in 'double' values:
-                 * with zero fraction digits in base 2 representation (the standard Java cast),
-                 * or with zero fraction digits in base 10 representation.
-                 */
-                final int length = size();
-                int i = 0;
-                double v;
-                do if (i >= length) {
-                    return new Floats(toFloatArray());
-                } while (!(Math.abs((v = doubleValue(i++)) - (float) v) > tolerance));    // Use '!' for accepting NaN.
-                /*
-                 * Same try than above loop, but now using base 10 representation.
-                 * This is a more costly computation.
-                 */
-                i = 0;
-                do if (i >= length) {
-                    return new Decimal(toFloatArray());
-                } while (!(Math.abs((v = doubleValue(i++)) - DecimalFunctions.floatToDouble((float) v)) > tolerance));
-            }
-        }
-        return vec;
+     * Returns a vector with the same data than the given vector but encoded in a more compact way,
+     * or {@code null} if this method can not do better than the given {@code Vector} instance.
+     * This method shall be invoked only for vector of integer values (this is not verified).
+     */
+    static Vector compress(final Vector source, final long min, final long max) {
+        boolean isSigned = (min >= Byte.MIN_VALUE && max <= Byte.MAX_VALUE);
+        if (isSigned || (min >= 0 && max <= 0xFF)) {
+            if (source instanceof Bytes) return null;
+            final byte[] array = new byte[source.size()];
+            for (int i=0; i < array.length; i++) {
+                array[i] = (byte) source.intValue(i);
+            }
+            return isSigned ? new Bytes(array) : new UnsignedBytes(array);
+        }
+        isSigned = (min >= Short.MIN_VALUE && max <= Short.MAX_VALUE);
+        if (isSigned || (min >= 0 && max <= 0xFFFF)) {
+            if (source instanceof Shorts) return null;
+            final short[] array = new short[source.size()];
+            for (int i=0; i < array.length; i++) {
+                array[i] = (short) source.intValue(i);
+            }
+            return isSigned ? new Shorts(array) : new UnsignedShorts(array);
+        }
+        isSigned = (min >= Integer.MIN_VALUE && max <= Integer.MAX_VALUE);
+        if (isSigned || (min >= 0 && max <= 0xFFFFFFFF)) {
+            if (source instanceof Integers) return null;
+            final int[] array = new int[source.size()];
+            for (int i=0; i < array.length; i++) {
+                array[i] = (int) source.longValue(i);
+            }
+            return isSigned ? new Integers(array) : new UnsignedIntegers(array);
+        }
+        if (!(source instanceof Longs) && !(source instanceof Floats) && !(source instanceof Doubles)) {
+            final long[] array = new long[source.size()];
+            for (int i=0; i < array.length; i++) {
+                array[i] = source.longValue(i);
+            }
+            return new Longs(array);
+        }
+        return null;
     }
 
     /**
-     * Returns a copy of current data as a floating point array.
-     */
-    float[] toFloatArray() {
-        final float[] copy = new float[size()];
-        for (int i=0; i<copy.length; i++) {
-            copy[i] = (float) doubleValue(i);
+     * Returns a vector with the same data than the given vector but encoded in a more compact way,
+     * or {@code null} if this method can not do better than the given {@code Vector} instance.
+     * This method shall be invoked only for vector of floating point values (this is not verified).
+     */
+    static Vector compress(final Vector source, final double tolerance) {
+        if (!Float.class.equals(source.getElementType())) {
+            /*
+             * For floating point types, verify if values are equivalent to 'float' values.
+             * There is two different ways to pad extra fraction digits in 'double' values:
+             * with zero fraction digits in base 2 representation (the standard Java cast),
+             * or with zero fraction digits in base 10 representation.
+             */
+            final int length = source.size();
+            int i = 0;
+            double v;
+            do if (i >= length) {
+                return new Floats(source.floatValues());
+            } while (!(Math.abs((v = source.doubleValue(i++)) - (float) v) > tolerance));    // Use '!' for accepting NaN.
+            /*
+             * Same try than above loop, but now using base 10 representation.
+             * This is a more costly computation.
+             */
+            i = 0;
+            do if (i >= length) {
+                return new Decimal(source.floatValues());
+            } while (!(Math.abs((v = source.doubleValue(i++)) - DecimalFunctions.floatToDouble((float) v)) > tolerance));
         }
-        return copy;
+        return null;
     }
 
     /**
@@ -269,11 +243,6 @@ abstract class ArrayVector<E extends Num
             return old;
         }
 
-        /** Returns a copy of current data as a floating point array. */
-        @Override float[] toFloatArray() {
-            return Numerics.copyAsFloats(array);
-        }
-
         /** Finds the minimum and maximum values in the array or in a subset of the array. */
         @Override NumberRange<Double> range(final IntSupplier indices, int n) {
             double min = Double.POSITIVE_INFINITY;
@@ -285,6 +254,16 @@ abstract class ArrayVector<E extends Num
             }
             return NumberRange.create(min, true, max, true);
         }
+
+        /** Returns a copy of current data as a floating point array. */
+        @Override public double[] doubleValues() {
+            return array.clone();
+        }
+
+        /** Returns a copy of current data as a floating point array. */
+        @Override public float[] floatValues() {
+            return Numerics.copyAsFloats(array);
+        }
     }
 
     /**
@@ -358,6 +337,11 @@ abstract class ArrayVector<E extends Num
         NumberRange<?> createRange(final float min, final float max) {
             return NumberRange.create(min, true, max, true);
         }
+
+        /** Returns a copy of current data as a floating point array. */
+        @Override public final float[] floatValues() {
+            return array.clone();
+        }
     }
 
     /**
@@ -412,6 +396,11 @@ abstract class ArrayVector<E extends Num
             return Long.class;
         }
 
+        /** Values in this vector are guaranteed to be integers. */
+        @Override public final boolean isInteger() {
+            return true;
+        }
+
         /** Returns the string representation at the given index. */
         @Override public String stringValue(final int index) {
             return Long.toString(array[index]);
@@ -493,6 +482,11 @@ abstract class ArrayVector<E extends Num
             return Integer.class;
         }
 
+        /** Values in this vector are guaranteed to be integers. */
+        @Override public final boolean isInteger() {
+            return true;
+        }
+
         /** Returns the string representation at the given index. */
         @Override public String stringValue(final int index) {
             return Integer.toString(array[index]);
@@ -578,6 +572,11 @@ abstract class ArrayVector<E extends Num
             return Short.class;
         }
 
+        /** Values in this vector are guaranteed to be integers. */
+        @Override public final boolean isInteger() {
+            return true;
+        }
+
         /** Returns the string representation at the given index. */
         @Override public String stringValue(final int index) {
             return Short.toString(array[index]);
@@ -638,6 +637,11 @@ abstract class ArrayVector<E extends Num
             return Byte.class;
         }
 
+        /** Values in this vector are guaranteed to be integers. */
+        @Override public final boolean isInteger() {
+            return true;
+        }
+
         /** Returns the string representation at the given index. */
         @Override public String stringValue(final int index) {
             return Byte.toString(array[index]);

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -136,7 +136,7 @@ public final class Fraction extends Numb
                 num /= den;                     // Simplify  xy/x  as  y/1
                 den = 1;
             } else {
-                do {                            // Search for greater common divisor with Euclid's algorithm.
+                do {                            // Search for greatest common divisor with Euclid's algorithm.
                     a   = gcd;
                     gcd = remainder;
                     remainder = a % gcd;

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -559,8 +559,9 @@ public final class MathFunctions extends
      * Some of those bits, named the <cite>payload</cite>, can be used for storing custom information.
      * This method maps some of the payload values to each ordinal value.
      *
-     * <p>The relationship between payload values and ordinal values is implementation dependent and
-     * may change in any future version of the SIS library. The current implementation restricts the
+     * <p>This method guarantees that {@code toNanFloat(0)} returns the standard {@link Float#NaN} value.
+     * For all other {@code ordinal} values, the relationship to the payload values is implementation dependent
+     * and may change in any future version of the SIS library. The current implementation restricts the
      * range of allowed ordinal values to a smaller one than the range of all possible values.</p>
      *
      * @param  ordinal  the NaN ordinal value, from {@code -0x200000} to {@code 0x1FFFFF} inclusive.
@@ -580,6 +581,10 @@ public final class MathFunctions extends
      * Returns the ordinal value of the given NaN number.
      * This method is the converse of {@link #toNanFloat(int)}.
      *
+     * <p>If the given float is the standard {@link Float#NaN} value, then this method returns 0.
+     * For all other values, the relationship between the float payload and the returned ordinal
+     * is implementation dependent and may change in any future Apache SIS version.</p>
+     *
      * @param  value  the value from which to get the NaN ordinal value.
      * @return the NaN ordinal value of the given floating point value.
      * @throws IllegalArgumentException if the given value is not a NaN value,

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/PackedVector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/PackedVector.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/PackedVector.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/PackedVector.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -18,6 +18,7 @@ package org.apache.sis.math;
 
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.collection.IntegerList;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -33,7 +34,13 @@ final class PackedVector extends ArrayVe
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 2025113345434607526L;
+    private static final long serialVersionUID = 5097586732924434042L;
+
+    /**
+     * Minimal length for creating a packed vector.
+     * This is an arbitrary value that may change in any future version.
+     */
+    static final int MINIMAL_SIZE = 8;
 
     /**
      * The compressed list of integer values. This list can store values from 0 to {@code delta} inclusive.
@@ -41,6 +48,11 @@ final class PackedVector extends ArrayVe
     private final IntegerList data;
 
     /**
+     * The value by which to multiply the {@linkplain #data} before to add the {@linkplain #offset}.
+     */
+    private final long increment;
+
+    /**
      * The offset to add to the {@link #data} in order to get the values to return.
      */
     private final long offset;
@@ -48,20 +60,84 @@ final class PackedVector extends ArrayVe
     /**
      * Creates a new compressed vector initialized to a copy of the data provided by the given vector.
      *
-     * @param  source  the vector to copy.
-     * @param  offset  the minimal value in the source vector.
-     * @param  delta   the maximal value in the source vector minus {@code offset}.
-     */
-    PackedVector(final Vector source, final long offset, final int delta) {
-        this.offset = offset;
+     * @param  source     the vector to copy.
+     * @param  increment  the common divisor of all (sample minus offset) values.
+     * @param  offset     the minimal value in the source vector.
+     * @param  delta      the maximal value in the source vector minus {@code offset} divided by {@code increment}.
+     */
+    private PackedVector(final Vector source, final long increment, final long offset, final int delta) {
+        this.increment = increment;
+        this.offset    = offset;
         final int length = source.size();
         data = new IntegerList(length, delta, true);
         for (int i=0; i<length; i++) {
-            data.setInt(i, Math.toIntExact(source.longValue(i) - offset));
+            data.setInt(i, Math.toIntExact((source.longValue(i) - offset) / increment));
         }
     }
 
     /**
+     * Creates a new compressed vector initialized to a copy of the data provided by the given vector.
+     * All values in the given vector shall be assignable to the {@code long} type (this is not verified).
+     *
+     * @param  source  the vector to copy.
+     * @param  min     the minimal value in the given vector, inclusive.
+     * @param  max     the maximal value in the given vector, inclusive.
+     * @return the compressed vector, or {@code null} if the vector can not or should not be compressed.
+     */
+    static PackedVector compress(final Vector source, final long min, final long max) {
+        long delta = max - min;
+        if (delta > 0) {                                    // Negative if (max - min) overflow.
+            long inc = delta;
+            final int length = source.size();
+            if (length >= MINIMAL_SIZE) {
+                for (int i=0; i<length; i++) {
+                    long t = source.longValue(i) - min;
+                    if (t < 0) return null;                 // May happen if the given 'min' value is wrong.
+                    if ((t % inc) != 0) {
+                        do {
+                            final long r = (inc % t);       // Search for greatest common divisor with Euclid's algorithm.
+                            inc = t;
+                            t = r;
+                        } while (t != 0);
+                        if (inc == 1) {
+                            /*
+                             * If we reach this point, the increment is of no use for compressing data.
+                             * If in addition the minimal number of bits required for storing all values:
+                             *
+                             *     numBits = Long.SIZE - Long.numberOfLeadingZeros(delta);
+                             *
+                             * is equal to the number of bits of a primitive type (byte, short, int or long)
+                             * and all values are in the range of that primitive type, then there is nothing
+                             * to win compared to an array of that primitive type.
+                             */
+                            final long high = Long.highestOneBit(delta);
+                            if ((high & ((1L << (Byte   .SIZE - 1)) |
+                                         (1L << (Short  .SIZE - 1)) |
+                                         (1L << (Integer.SIZE - 1)) |
+                                         (1L << (Long   .SIZE - 1)))) != 0)
+                            {
+                                long limit = high - 1;                  // Maximal value of signed integers.
+                                if (min >= 0) {
+                                    limit |= high;                      // Maximal value of unsigned integers.
+                                    if (limit < 0) return null;         // Overflow the 'long' primitve type.
+                                }
+                                if (max <= limit && min >= ~limit) {    // Really tild (~), not minus (-).
+                                    return null;                        // All values in range of primitive type.
+                                }
+                            }
+                            break;                                      // No need to check other values.
+                        }
+                    }
+                }
+                delta /= inc;
+                if (delta > Integer.MAX_VALUE) return null;
+                return new PackedVector(source, inc, min, (int) delta);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Type of elements fixed to {@code Long} even if the actual storage used by this class is more compact.
      * The reason for the {@code Long} type is that this class can return any value in the {@code Long} range,
      * because of the {@link #offset}.
@@ -100,7 +176,7 @@ final class PackedVector extends ArrayVe
      */
     @Override
     public long longValue(final int index) {
-        return data.getInt(index) + offset;
+        return data.getInt(index) * increment + offset;
     }
 
     /**
@@ -125,8 +201,19 @@ final class PackedVector extends ArrayVe
     @Override
     public Number set(final int index, final Number value) {
         verifyType(value.getClass(), Numbers.LONG);
-        final Number old = get(index);
-        data.setInt(index, Math.toIntExact(Math.subtractExact(value.longValue(), offset)));
-        return old;
+        long v = value.longValue();
+        if (v >= offset) {
+            v -= offset;
+            if ((v % increment) == 0) {
+                v /= increment;
+                if (v <= data.maximalValue()) {
+                    final Number old = get(index);
+                    data.setInt(index, (int) v);
+                    modCount++;
+                    return old;
+                }
+            }
+        }
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.CanNotStoreInVector_1, value));
     }
 }

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -16,8 +16,10 @@
  */
 package org.apache.sis.math;
 
+import java.util.Arrays;
 import java.io.Serializable;
 import org.apache.sis.measure.NumberRange;
+import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 
@@ -35,7 +37,12 @@ abstract class SequenceVector extends Ve
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 7980737287789566091L;
+    private static final long serialVersionUID = 2544089499300079707L;
+
+    /**
+     * The type of values in the vector.
+     */
+    final Class<? extends Number> type;
 
     /**
      * The length of this vector.
@@ -45,7 +52,8 @@ abstract class SequenceVector extends Ve
     /**
      * Creates a sequence of numbers of the given length.
      */
-    SequenceVector(final int length) {
+    SequenceVector(final Class<? extends Number> type, final int length) {
+        this.type   = type;
         this.length = length;
         if (length < 0) {
             throw new IllegalArgumentException(Errors.format(
@@ -54,6 +62,14 @@ abstract class SequenceVector extends Ve
     }
 
     /**
+     * Returns the type of elements.
+     */
+    @Override
+    public final Class<? extends Number> getElementType() {
+        return type;
+    }
+
+    /**
      * {@code SequenceVector} values are always interpreted as signed values.
      */
     @Override
@@ -74,7 +90,7 @@ abstract class SequenceVector extends Ve
      */
     @Override
     public final Number set(final int index, final Number value) {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "Vector"));
+        throw new UnsupportedOperationException(Errors.format(Errors.Keys.CanNotStoreInVector_1, value));
     }
 
     /**
@@ -86,6 +102,32 @@ abstract class SequenceVector extends Ve
         return this;
     }
 
+    /**
+     * Creates the sequence as a floating point array.
+     */
+    @Override
+    public double[] doubleValues() {
+        if (increment(0).doubleValue() == 0) {
+            final double[] array = new double[size()];
+            Arrays.fill(array, doubleValue(0));
+            return array;
+        }
+        return super.doubleValues();
+    }
+
+    /**
+     * Creates the sequence as a floating point array.
+     */
+    @Override
+    public float[] floatValues() {
+        if (increment(0).doubleValue() == 0) {
+            final float[] array = new float[size()];
+            Arrays.fill(array, floatValue(0));
+            return array;
+        }
+        return super.floatValues();
+    }
+
 
     /**
      * A vector which is a sequence of increasing or decreasing {@code double} values.
@@ -110,24 +152,20 @@ abstract class SequenceVector extends Ve
         /**
          * Creates a sequence of numbers in a given range of values using the given increment.
          *
+         * @param  type       the type of elements in the sequence.
          * @param  first      the first value, inclusive.
          * @param  increment  the difference between the values at two adjacent indexes.
          * @param  length     the length of the vector.
          */
-        Doubles(final Number first, final Number increment, final int length) {
-            super(length);
+        Doubles(final Class<? extends Number> type, final Number first, final Number increment, final int length) {
+            super(type, length);
             this.first     = first.doubleValue();
             this.increment = increment.doubleValue();
         }
 
         /** Creates a new sequence for a subrange of this vector. */
         @Override Vector createSubSampling(final int offset, final int step, final int n) {
-            return new Doubles(doubleValue(offset), increment*step, n);
-        }
-
-        /** Returns the type of elements. */
-        @Override public Class<Double> getElementType() {
-            return Double.class;
+            return new Doubles(type, doubleValue(offset), increment*step, n);
         }
 
         /** Returns {@code true} if this vector contains only integer values. */
@@ -160,23 +198,24 @@ abstract class SequenceVector extends Ve
 
         /** Computes the value at the given index. */
         @Override public Number get(final int index) {
-            return doubleValue(index);
+            return Numbers.wrap(doubleValue(index), type);
         }
 
         /** Returns the increment between all consecutive values */
         @Override public Number increment(final double tolerance) {
-            return increment;
+            return Numbers.wrap(increment, type);
         }
 
         /** Computes the minimal and maximal values in this vector. */
-        @Override public NumberRange<Double> range() {
+        @SuppressWarnings({"unchecked","rawtypes"})
+        @Override public NumberRange<?> range() {
             double min = first;
             double max = first + increment * (length - 1);
             if (max < min) {
                 min = max;
                 max = first;
             }
-            return NumberRange.create(min, true, max, true);
+            return new NumberRange(type, Numbers.wrap(min, type), true, Numbers.wrap(max, type), true);
         }
     }
 
@@ -204,24 +243,20 @@ abstract class SequenceVector extends Ve
         /**
          * Creates a sequence of numbers in a given range of values using the given increment.
          *
+         * @param  type       the type of elements in the sequence.
          * @param  first      the first value, inclusive.
          * @param  increment  the difference between the values at two adjacent indexes.
          * @param  length     the length of the vector.
          */
-        Longs(final Number first, final Number increment, final int length) {
-            super(length);
+        Longs(final Class<? extends Number> type, final Number first, final Number increment, final int length) {
+            super(type, length);
             this.first     = first.longValue();
             this.increment = increment.longValue();
         }
 
         /** Creates a new sequence for a subrange of this vector. */
         @Override Vector createSubSampling(final int offset, final int step, final int n) {
-            return new Longs(longValue(offset), increment*step, n);
-        }
-
-        /** Returns the type of elements. */
-        @Override public Class<Long> getElementType() {
-            return Long.class;
+            return new Longs(type, longValue(offset), increment*step, n);
         }
 
         /** Returns {@code true} since this vector contains only integer values. */
@@ -257,23 +292,24 @@ abstract class SequenceVector extends Ve
 
         /** Computes the value at the given index. */
         @Override public Number get(final int index) {
-            return longValue(index);
+            return Numbers.wrap(longValue(index), type);
         }
 
         /** Returns the increment between all consecutive values */
         @Override public Number increment(final double tolerance) {
-            return increment;
+            return Numbers.wrap(increment, type);
         }
 
         /** Computes the minimal and maximal values in this vector. */
-        @Override public NumberRange<Long> range() {
+        @SuppressWarnings({"unchecked","rawtypes"})
+        @Override public NumberRange<?> range() {
             long min = first;
             long max = first + increment * (length - 1);
             if (max < min) {
                 min = max;
                 max = first;
             }
-            return NumberRange.create(min, true, max, true);
+            return new NumberRange(type, Numbers.wrap(min, type), true, Numbers.wrap(max, type), true);
         }
     }
 }

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -174,12 +174,19 @@ public abstract class Vector extends Abs
         type = Numbers.widestClass(first, increment);
         type = Numbers.widestClass(type,
                Numbers.narrowestClass(first.doubleValue() + increment.doubleValue() * (length-1)));
+        return createSequence(type, first, increment, length);
+    }
+
+    /**
+     * Creates a sequence of the given type.
+     */
+    static Vector createSequence(final Class<? extends Number> type, final Number first, final Number increment, final int length) {
         final int t = Numbers.getEnumConstant(type);
         if (t >= Numbers.BYTE && t <= Numbers.LONG) {
             // Use the long type if possible because not all long values can be represented as double.
-            return new SequenceVector.Longs(first, increment, length);
+            return new SequenceVector.Longs(type, first, increment, length);
         } else {
-            return new SequenceVector.Doubles(first, increment, length);
+            return new SequenceVector.Doubles(type, first, increment, length);
         }
     }
 
@@ -195,6 +202,20 @@ public abstract class Vector extends Abs
      * is backed by an array of type {@code float[]}, then this method returns {@code Float.class},
      * not {@link Float#TYPE}.
      *
+     * <p>The information returned by this method is only indicative; it is not guaranteed to specify accurately
+     * this kind of objects returned by the {@link #get(int)} method. There is various situation where the types
+     * may not match:</p>
+     *
+     * <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>
+     *   <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>
+     *   <li>If this vector {@linkplain #compress(double) has been compressed}, then the type returned by this
+     *       method does not describe accurately the range of values that this vector can store.</li>
+     * </ul>
+     *
      * <p>Users of the {@link #doubleValue(int)} method do not need to care about this information since
      * {@code Vector} will perform automatically the type conversion. Users of other methods may want to
      * verify this information for avoiding {@link ArithmeticException}.</p>
@@ -262,6 +283,8 @@ public abstract class Vector extends Abs
      * @throws IndexOutOfBoundsException if the given index is out of bounds.
      * @throws NullPointerException if the value is {@code null} (never happen if this vector wraps an array of primitive type).
      * @throws NumberFormatException if the value is stored as a {@code String} and can not be parsed.
+     *
+     * @see #doubleValues()
      */
     public abstract double doubleValue(int index);
 
@@ -275,6 +298,8 @@ public abstract class Vector extends Abs
      * @throws IndexOutOfBoundsException if the given index is out of bounds.
      * @throws NullPointerException if the value is {@code null} (never happen if this vector wraps an array of primitive type).
      * @throws NumberFormatException if the value is stored as a {@code String} and can not be parsed.
+     *
+     * @see #floatValues()
      */
     public abstract float floatValue(int index);
 
@@ -388,6 +413,8 @@ public abstract class Vector extends Abs
      * @param  index  the index in the [0 … {@linkplain #size() size}-1] range.
      * @return a string representation of the value at the given index (may be {@code null}).
      * @throws IndexOutOfBoundsException if the given index is out of bounds.
+     *
+     * @see #toString()
      */
     public abstract String stringValue(int index);
 
@@ -412,14 +439,16 @@ public abstract class Vector extends Abs
     /**
      * Sets the number at the given index.
      * The given number should be an instance of the same type than the number returned by {@link #get(int)}.
+     * If not, the stored value may lost precision as a result of the cast.
      *
      * @param  index  the index in the [0 … {@linkplain #size() size}-1] range.
      * @param  value  the value to set at the given index.
      * @return the value previously stored at the given index.
+     * @throws UnsupportedOperationException if this vector is read-only.
      * @throws IndexOutOfBoundsException if the given index is out of bounds.
      * @throws NumberFormatException if the previous value was stored as a {@code String} and can not be parsed.
-     * @throws ClassCastException if the given value can not be converted to the type expected by this vector.
-     * @throws ArrayStoreException if the given value can not be stored in this vector.
+     * @throws IllegalArgumentException if this vector uses some {@linkplain #compress(double) compression} technic
+     *         and the given value is out of range for that compression.
      */
     @Override
     public abstract Number set(int index, Number value);
@@ -956,8 +985,8 @@ public abstract class Vector extends Abs
      *
      * <div class="section">When to use</div>
      * It is usually not worth to compress small arrays. Performance-critical arrays may not be compressed neither.
-     * This method is best suited for arrays that may potentially be large and for which the cost of reading that
-     * array is small compared to the calculation performed with the values.
+     * This method is best suited for vectors that may potentially be large and for which the cost of fetching
+     * values in that vector is small compared to the calculation performed with the values.
      *
      * @param  tolerance  maximal difference allowed between original and compressed vectors (can be zero).
      * @return a more compact vector with the same data than this vector, or {@code this}.
@@ -967,18 +996,45 @@ public abstract class Vector extends Abs
         final int length = size();
         final Number inc = increment(tolerance);
         if (inc != null) {
-            return createSequence(get(0), inc, length);
+            return createSequence(getElementType(), get(0), inc, length);
         }
         /*
          * Verify if the vector contains only NaN values. This extra check is useful because 'increment()'
          * returns null if the array contains NaN. Note that for array of integers, 'isNaN(int)' is very
          * efficient and the loop will stop immediately after the first iteration.
          */
-        for (int i=0; i<length; i++) {
-            if (!isNaN(i)) return this;
+        int i = 0;
+        do if (i >= length) {
+            final Double NaN = Numerics.valueOf(Double.NaN);
+            return new SequenceVector.Doubles(getElementType(), NaN, NaN, length);
+        } while (isNaN(i++));
+        /*
+         * Try to copy the values in a more compact format.
+         * We will use a vector backed by IntegerList in order to use only the amount of bits needed,
+         * unless that amount is exactly the number of bits of a primitive type (8, 16, 32 or 64) in
+         * which case using one of the specialized classes in this ArrayVector is more performant.
+         */
+        final NumberRange<?> range = range();
+        if (range != null && !range.isEmpty()) {
+            final Number min = range.getMinValue();
+            final Number max = range.getMaxValue();
+            final boolean isInteger = (min.doubleValue() >= Long.MIN_VALUE &&
+                                       max.doubleValue() <= Long.MAX_VALUE &&
+                                       isInteger());                                // May scan the vector.
+            Vector vec;
+            if (isInteger) {
+                vec = PackedVector.compress(this, min.longValue(), max.longValue());
+                if (vec == null) {
+                    vec = ArrayVector.compress(this, min.longValue(), max.longValue());
+                }
+            } else {
+                vec = ArrayVector.compress(this, tolerance);
+            }
+            if (vec != null) {
+                return vec;
+            }
         }
-        final Double NaN = Numerics.valueOf(Double.NaN);
-        return new SequenceVector.Doubles(NaN, NaN, length);
+        return this;
     }
 
     /**
@@ -989,9 +1045,49 @@ public abstract class Vector extends Abs
     }
 
     /**
+     * Copies all values in an array of double precision floating point numbers.
+     * This method is for inter-operability with APIs requiring an array of primitive type.
+     *
+     * <p>The default implementation invokes {@link #doubleValue(int)} for all indices from 0 inclusive
+     * to {@link #size()} exclusive. Subclasses may override with more efficient implementation.</p>
+     *
+     * @return a copy of all floating point values in this vector.
+     *
+     * @see #doubleValue(int)
+     */
+    public double[] doubleValues() {
+        final double[] array = new double[size()];
+        for (int i=0; i<array.length; i++) {
+            array[i] = doubleValue(i);
+        }
+        return array;
+    }
+
+    /**
+     * Copies all values in an array of single precision floating point numbers.
+     * This method is for inter-operability with APIs requiring an array of primitive type.
+     *
+     * <p>The default implementation invokes {@link #floatValue(int)} for all indices from 0 inclusive
+     * to {@link #size()} exclusive. Subclasses may override with more efficient implementation.</p>
+     *
+     * @return a copy of all floating point values in this vector.
+     *
+     * @see #floatValue(int)
+     */
+    public float[] floatValues() {
+        final float[] array = new float[size()];
+        for (int i=0; i<array.length; i++) {
+            array[i] = floatValue(i);
+        }
+        return array;
+    }
+
+    /**
      * Returns a string representation of this vector.
      *
      * @return a string representation of this vector.
+     *
+     * @see #stringValue(int)
      */
     @Override
     public String toString() {

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -127,6 +127,13 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Returns {@code true} if the use of SI prefixes is allowed for this unit.
+     */
+    final boolean isPrefixable() {
+        return (scope & UnitRegistry.PREFIXABLE) != 0;
+    }
+
+    /**
      * Returns the symbol (if any) of this unit. A unit may have no symbol, in which case
      * the {@link #toString()} method is responsible for creating a string representation.
      *
@@ -173,6 +180,15 @@ abstract class AbstractUnit<Q extends Qu
     public abstract SystemUnit<Q> getSystemUnit();
 
     /**
+     * Returns the base units and their exponent whose product is the system unit,
+     * or {@code null} if the system unit is a base unit (not a product of existing units).
+     *
+     * @return the base units and their exponent making up the system unit.
+     */
+    @Override
+    public abstract Map<SystemUnit<?>, Integer> getBaseUnits();
+
+    /**
      * Returns the base units used by Apache SIS implementations.
      * Contrarily to {@link #getBaseUnits()}, this method never returns {@code null}.
      */
@@ -283,6 +299,14 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Returns units for the same quantity but with scale factors that are not the SI one, or {@code null} if none.
+     * This method returns a direct reference to the internal field; caller shall not modify.
+     */
+    ConventionalUnit<Q>[] related() {
+        return null;
+    }
+
+    /**
      * Compares this unit with the given object for equality.
      *
      * @param  other  the other object to compare with this unit, or {@code null}.

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -62,9 +62,11 @@ final class ConventionalUnit<Q extends Q
 
     /**
      * The base, derived or alternate units to which this {@code ConventionalUnit} is related.
-     * This is called "preferred unit" in GML.
+     * This is called "preferred unit" in GML. This is usually an instance of {@link SystemUnit},
+     * but may also be another {@link ConventionalUnit} in some rare cases where this conventional
+     * unit can be prefixed like a SI units (e.g. litre: L, cl, mL, µL).
      */
-    final SystemUnit<Q> target;
+    private final AbstractUnit<Q> target;
 
     /**
      * The conversion from this unit to the {@linkplain #target} unit.
@@ -80,7 +82,7 @@ final class ConventionalUnit<Q extends Q
      * @param  scope     {@link UnitRegistry#SI}, {@link UnitRegistry#ACCEPTED}, other constants or 0 if unknown.
      * @param  epsg      the EPSG code, or 0 if this unit has no EPSG code.
      */
-    ConventionalUnit(final SystemUnit<Q> target, final UnitConverter toTarget, final String symbol, final byte scope, final short epsg) {
+    ConventionalUnit(final AbstractUnit<Q> target, final UnitConverter toTarget, final String symbol, final byte scope, final short epsg) {
         super(symbol, scope, epsg);
         this.target   = target;
         this.toTarget = toTarget;
@@ -88,9 +90,12 @@ final class ConventionalUnit<Q extends Q
 
     /**
      * Creates a new unit with default name and symbol for the given converter.
+     *
+     * @param  target    the base or derived units to which the new unit will be related.
+     * @param  toTarget  the conversion from the new unit to the {@code target} unit.
      */
     @SuppressWarnings("unchecked")
-    static <Q extends Quantity<Q>> AbstractUnit<Q> create(final SystemUnit<Q> target, final UnitConverter toTarget) {
+    static <Q extends Quantity<Q>> AbstractUnit<Q> create(final AbstractUnit<Q> target, final UnitConverter toTarget) {
         if (toTarget.isIdentity()) {
             return target;
         }
@@ -115,7 +120,7 @@ final class ConventionalUnit<Q extends Q
          * unit instances.
          */
         String symbol = null;
-        if (target.scope == UnitRegistry.SI) {
+        if (target.isPrefixable()) {
             final String ts = target.getSymbol();
             if (ts != null && !ts.isEmpty() && toTarget.isLinear()) {
                 final int power = power(ts);
@@ -251,7 +256,7 @@ final class ConventionalUnit<Q extends Q
      */
     @Override
     public Dimension getDimension() {
-        return target.dimension;
+        return target.getDimension();
     }
 
     /**
@@ -259,7 +264,7 @@ final class ConventionalUnit<Q extends Q
      */
     @Override
     public SystemUnit<Q> getSystemUnit() {
-        return target;
+        return target.getSystemUnit();
     }
 
     /**
@@ -317,11 +322,12 @@ final class ConventionalUnit<Q extends Q
         UnitConverter c = toTarget;
         if (target != that) {                           // Optimization for a common case.
             final Unit<Q> step = that.getSystemUnit();
-            if (target != step && !target.equalsIgnoreMetadata(step)) {
+            if (target != step && !target.isCompatible(step)) {
                 // Should never occur unless parameterized type has been compromised.
                 throw new UnconvertibleException(incompatible(that));
             }
-            c = step.getConverterTo(that).concatenate(c);
+            c = target.getConverterTo(step).concatenate(c);         // Usually leave 'c' unchanged.
+            c =   step.getConverterTo(that).concatenate(c);
         }
         return c;
     }
@@ -349,7 +355,8 @@ final class ConventionalUnit<Q extends Q
             if (target != step && !target.isCompatible(step)) {
                 throw new IncommensurableException(incompatible(that));
             }
-            c = step.getConverterToAny(that).concatenate(c);
+            c = target.getConverterToAny(step).concatenate(c);      // Usually leave 'c' unchanged.
+            c =   step.getConverterToAny(that).concatenate(c);
         }
         return c;
     }
@@ -435,9 +442,14 @@ final class ConventionalUnit<Q extends Q
      * @return the unit after the specified transformation.
      */
     @Override
-    public Unit<Q> transform(final UnitConverter operation) {
+    public Unit<Q> transform(UnitConverter operation) {
         ArgumentChecks.ensureNonNull("operation", operation);
-        return create(target, toTarget.concatenate(operation));
+        AbstractUnit<Q> base = this;
+        if (!isPrefixable()) {
+            base = target;
+            operation = toTarget.concatenate(operation);
+        }
+        return create(base, operation);
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -468,9 +468,15 @@ final class SystemUnit<Q extends Quantit
      * @return the unit after the specified transformation.
      */
     @Override
-    public Unit<Q> transform(final UnitConverter operation) {
+    @SuppressWarnings("unchecked")
+    public Unit<Q> transform(UnitConverter operation) {
         ArgumentChecks.ensureNonNull("operation", operation);
-        return ConventionalUnit.create(this, operation);
+        AbstractUnit<Q> base = this;
+        if (this == Units.KILOGRAM) {
+            base = (AbstractUnit<Q>) Units.GRAM;
+            operation = operation.concatenate(LinearConverter.forPrefix('k'));
+        }
+        return ConventionalUnit.create(base, operation);
     }
 
     /**
@@ -486,9 +492,10 @@ final class SystemUnit<Q extends Quantit
     }
 
     /**
-     * Returns units for the same quantity but with scale factors that are not the SI one.
+     * Returns units for the same quantity but with scale factors that are not the SI one, or {@code null} if none.
      * This method returns a direct reference to the internal field; caller shall not modify.
      */
+    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     final ConventionalUnit<Q>[] related() {
         return related;

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -85,6 +85,12 @@ public class UnitFormat extends Format i
     private static final long serialVersionUID = -3064428584419360693L;
 
     /**
+     * The unit name for degrees (not necessarily angular), to be handled in a special way.
+     * Must contain only ASCII lower case letters ([a … z]).
+     */
+    private static final String DEGREES = "degrees";
+
+    /**
      * The unit name for dimensionless unit.
      */
     static final String UNITY = "unity";
@@ -415,13 +421,17 @@ public class UnitFormat extends Format i
         final Unit<?> unitForOldLabel = labelToUnit.remove(unitToLabel.put(unit, label));
         final Unit<?> oldUnitForLabel = labelToUnit.put(label, unit);
         if (oldUnitForLabel != null && !oldUnitForLabel.equals(unit) && !label.equals(unitToLabel.remove(oldUnitForLabel))) {
-            // Assuming there is no bug in our algorithm, this exception should never happen
-            // unless this UnitFormat has been modified concurrently in another thread.
+            /*
+             * Assuming there is no bug in our algorithm, this exception should never happen
+             * unless this UnitFormat has been modified concurrently in another thread.
+             */
             throw new CorruptedObjectException("unitToLabel");
         }
         if (unitForOldLabel != null && !unitForOldLabel.equals(unit)) {
-            // Assuming there is no bug in our algorithm, this exception should never happen
-            // unless this UnitFormat has been modified concurrently in another thread.
+            /*
+             * Assuming there is no bug in our algorithm, this exception should never happen
+             * unless this UnitFormat has been modified concurrently in another thread.
+             */
             throw new CorruptedObjectException("labelToUnit");
         }
     }
@@ -453,8 +463,9 @@ public class UnitFormat extends Format i
      *
      * <p>While we said that {@code UnitFormat} is not thread safe, we make an exception for this method
      * for allowing the singleton {@link #INSTANCE} to parse symbols in a multi-threads environment.</p>
+     *
+     * @param  uom  the unit symbol, without leading or trailing spaces.
      */
-    @SuppressWarnings("fallthrough")
     private Unit<?> fromName(String uom) {
         /*
          * Before to search in resource bundles, check for degrees units. The "deg" unit can be both angular
@@ -462,20 +473,38 @@ public class UnitFormat extends Format i
          * special case for the degrees units because SI symbols are case-sentive and unit names in resource
          * bundles are case-insensitive, but the "deg" case is a mix of both.
          */
-        if (uom.regionMatches(true, 0, "deg", 0, 3)) {
-            final int length = uom.length();
-            switch (length) {
-                case 3: return Units.DEGREE;                    // Exactly "deg"  (ignoring case)
-                case 5: final char c = uom.charAt(3);
-                        if (c != '_' && !Character.isSpaceChar(c)) break;
-                        // else fallthrough
-                case 4: switch (uom.charAt(length - 1)) {
-                            case 'K':                           // Unicode U+212A
-                            case 'K': return Units.KELVIN;      // Exactly "degK" (ignoring case except for 'K')
-                            case 'C': return Units.CELSIUS;
-                        }
+        final int length = uom.length();
+        for (int i=0; ; i++) {
+            if (i != DEGREES.length()) {
+                if (i != length && (uom.charAt(i) | ('a' - 'A')) == DEGREES.charAt(i)) {
+                    continue;                           // Loop as long as the characters are the same, ignoring case.
+                }
+                if (i != 3 && i != 6) {
+                    break;                              // Exit if not "deg" (3) or "degree" (6 characters).
+                }
             }
+            if (length == i) {
+                return Units.DEGREE;                    // Exactly "deg", "degree" or "degrees" (ignoring case).
+            }
+            final int c = uom.codePointAt(i);
+            if (c == '_' || Character.isSpaceChar(c)) {
+                i += Character.charCount(c);            // Ignore space in "degree C", "deg C", "deg K", etc.
+            }
+            if (length - i == 1) {
+                switch (uom.charAt(i)) {
+                    case 'K':                           // Unicode U+212A
+                    case 'K': return Units.KELVIN;      // "degK" (ignoring case except for 'K')
+                    case 'C': return Units.CELSIUS;
+                    case 'N':                           // degree_N, degrees_N, degreeN, degreesN.
+                    case 'E': return Units.DEGREE;      // degree_E, degrees_E, degreeE, degreesE.
+                }
+            }
+            break;
         }
+        /*
+         * At this point, we determined that the given unit symbol is not degrees (of angle or of temperature).
+         * Remaining code is generic to all other kinds of units: a check in a HashMap loaded when first needed.
+         */
         Map<String,Unit<?>> map = nameToUnit;
         if (map == null) {
             map = SHARED.get(locale);
@@ -520,7 +549,7 @@ public class UnitFormat extends Format i
         uom = CharSequences.replace(CharSequences.replace(CharSequences.replace(CharSequences.toASCII(uom),
                 "meters",  "meter"),
                 "metres",  "metre"),
-                "degrees", "degree").toString();
+                 DEGREES,  "degree").toString();
         return map.get(uom);
     }
 
@@ -782,14 +811,32 @@ public class UnitFormat extends Format i
     }
 
     /**
-     * Returns {@code true} if the {@code '*'} character at the given index is surrounded by digits
-     * or a sign on its right side. For example this method returns {@code true} for "10*-6", which
-     * means 1E-6 in UCUM syntax. This check is used for heuristic rules at parsing time.
-     */
-    private static boolean isExponentOperator(final CharSequence symbols, int i, final int length) {
-        char c;
-        return (i != 0) && isDigit(symbols.charAt(i-1)) &&
-               (++i < length) && (isDigit(c = symbols.charAt(i)) || isSign(c));
+     * Returns {@code 0} or {@code 1} if the {@code '*'} character at the given index stands for exponentiation
+     * instead than multiplication, or a negative value if the character stands for multiplication. This check
+     * is used for heuristic rules at parsing time. Current implementation applies the following rules:
+     *
+     * <ul>
+     *   <li>The operation is presumed an exponentiation if the '*' symbol is doubled, as in {@code "m**s-1"}.</li>
+     *   <li>The operation is presumed an exponentiation if it is surrounded by digits or a sign on its right side.
+     *       Example: {@code "10*-6"}, which means 1E-6 in UCUM syntax.</li>
+     *   <li>All other cases are currently presumed multiplication.
+     *       Example: {@code "m*s"}.</li>
+     * </ul>
+     *
+     * @return -1 for parsing as a multiplication, or a positive value for exponentiation.
+     *         If positive, this is the number of characters in the exponent symbol minus 1.
+     */
+    private static int exponentOperator(final CharSequence symbols, int i, final int length) {
+        if (i >= 0 && ++i < length) {
+            final char c = symbols.charAt(i);
+            if (c == Style.EXPONENT_OR_MULTIPLY) {
+                return 1;                               // "**" operator: need to skip one character after '*'.
+            }
+            if ((isDigit(c) || isSign(c)) && isDigit(symbols.charAt(i-2))) {
+                return 0;                               // "*" operator surrounded by digits: no character to skip.
+            }
+        }
+        return -1;
     }
 
     /**
@@ -986,10 +1033,12 @@ scan:   for (int n; i < end; i += n) {
                  * a unit symbol.
                  */
                 case Style.EXPONENT_OR_MULTIPLY: {
-                    if (!isExponentOperator(symbols, i, end)) {
+                    final int w = exponentOperator(symbols, i, end);
+                    if (w < 0) {
                         next = MULTIPLY;
                         break;
                     }
+                    i += w;
                     // else fall through.
                 }
                 case Style.EXPONENT: {
@@ -1039,7 +1088,7 @@ scan:   for (int n; i < end; i += n) {
              * the above 'switch' statement all cases that end with 'break', not 'break scan' or 'continue').
              */
             if (operation != IMPLICIT) {
-                unit = apply(operation, unit, parseSymbol(symbols, start, i));
+                unit = apply(operation, unit, parseTerm(symbols, start, i));
             }
             hasSpaces = false;
             operation = next;
@@ -1085,7 +1134,7 @@ search:     while ((i = CharSequences.sk
             }
         }
         if (component == null) {
-            component = parseSymbol(symbols, start, i);
+            component = parseTerm(symbols, start, i);
         }
         unit = apply(operation, unit, component);
         position.setIndex(endOfURI >= 0 ? endOfURI : i);
@@ -1125,7 +1174,8 @@ search:     while ((i = CharSequences.sk
      * @return the parsed unit symbol (never {@code null}).
      * @throws ParserException if a problem occurred while parsing the given symbols.
      */
-    private Unit<?> parseSymbol(final CharSequence symbols, final int lower, final int upper) throws ParserException {
+    @SuppressWarnings("fallthrough")
+    private Unit<?> parseTerm(final CharSequence symbols, final int lower, final int upper) throws ParserException {
         final String uom = CharSequences.trimWhitespaces(symbols, lower, upper).toString();
         /*
          * Check for labels explicitly given by users. Those labels have precedence over the Apache SIS hard-coded
@@ -1157,11 +1207,11 @@ search:     while ((i = CharSequences.sk
                                 final int next = CharSequences.skipLeadingWhitespaces(uom, s, length);
                                 if (next < length && AbstractUnit.isSymbolChar(uom.codePointAt(next))) {
                                     multiplier = Double.parseDouble(uom.substring(0, s));
-                                    return parseSymbol(uom, s, length).multiply(multiplier);
+                                    return parseTerm(uom, s, length).multiply(multiplier);
                                 }
                             }
-                            s = uom.lastIndexOf(Style.EXPONENT_OR_MULTIPLY);
-                            if (s >= 0) {
+                            s = uom.lastIndexOf(Style.EXPONENT_OR_MULTIPLY);      // Check standard UCUM symbol first.
+                            if (s >= 0 || (s = uom.lastIndexOf(Style.EXPONENT)) >= 0) {
                                 final int base = Integer.parseInt(uom.substring(0, s));
                                 final int exp  = Integer.parseInt(uom.substring(s+1));
                                 multiplier = Math.pow(base, exp);
@@ -1209,7 +1259,24 @@ search:     while ((i = CharSequences.sk
                         } while (i != 0);
                     }
                     if (canApply) {
-                        unit = getPrefixed(CharSequences.trimWhitespaces(uom, 0, i).toString());
+                        /*
+                         * At this point we have parsed the exponent. Before to parse the raw unit symbol,
+                         * skip the exponent symbol (^, * or **) if any.
+                         */
+                        i = CharSequences.skipTrailingWhitespaces(uom, 0, i);
+                        if (i != 0) {
+                            switch (uom.charAt(i-1)) {
+                                case Style.EXPONENT_OR_MULTIPLY: {
+                                    if (i != 1 && uom.charAt(i-2) == Style.EXPONENT_OR_MULTIPLY) i--;
+                                    // Fallthrough for skipping the next character and whitespaces.
+                                }
+                                case Style.EXPONENT: {
+                                    i = CharSequences.skipTrailingWhitespaces(uom, 0, i - 1);
+                                    break;
+                                }
+                            }
+                        }
+                        unit = getPrefixed(uom.substring(CharSequences.skipLeadingWhitespaces(uom, 0, i), i));
                         if (unit != null) {
                             return unit.pow(power);
                         }
@@ -1246,7 +1313,7 @@ search:     while ((i = CharSequences.sk
                 s = 2;
             }
             unit = Units.get(uom.substring(s));
-            if (unit instanceof SystemUnit<?> && ((SystemUnit<?>) unit).scope == UnitRegistry.SI) {
+            if (unit instanceof AbstractUnit<?> && ((AbstractUnit<?>) unit).isPrefixable()) {
                 final LinearConverter c = LinearConverter.forPrefix(prefix);
                 if (c != null) {
                     String symbol = unit.getSymbol();
@@ -1255,7 +1322,7 @@ search:     while ((i = CharSequences.sk
                     } else {
                         symbol = prefix + symbol;
                     }
-                    return new ConventionalUnit<>((SystemUnit<?>) unit, c, symbol.intern(), (byte) 0, (short) 0);
+                    return new ConventionalUnit<>((AbstractUnit<?>) unit, c, symbol.intern(), (byte) 0, (short) 0);
                 }
             }
             unit = null;

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -48,25 +48,32 @@ final class UnitRegistry implements Syst
     private static final long serialVersionUID = -84557361079506390L;
 
     /**
+     * A bitmask specifying that the unit symbol can be combined with a SI prefix.
+     * This is usually combined only with {@link #SI}, not {@link #ACCEPTED} except
+     * the litre unit (cL, mL, etc).
+     */
+    static final byte PREFIXABLE = 1;
+
+    /**
      * Identifies units defined by the SI system.
      * All {@link SystemUnit} instances with this code can have a SI prefix.
      */
-    static final byte SI = 1;
+    static final byte SI = 2;
 
     /**
      * Identifies units defined outside the SI system but accepted for use with SI.
      */
-    static final byte ACCEPTED = 2;
+    static final byte ACCEPTED = 4;
 
     /**
      * Identifies units defined for use in British imperial system.
      */
-    static final byte IMPERIAL = 4;
+    static final byte IMPERIAL = 8;
 
     /**
      * Identifies units defined in another system than the above.
      */
-    static final byte OTHER = 8;
+    static final byte OTHER = 16;
 
     /**
      * All {@link UnitDimension}, {@link SystemUnit} or {@link ConventionalUnit} that are hard-coded in Apache SIS.



Mime
View raw message