sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1766738 - in /sis/branches/JDK8/core/sis-utility/src: main/java/org/apache/sis/measure/ main/resources/org/apache/sis/measure/ test/java/org/apache/sis/measure/
Date Wed, 26 Oct 2016 21:28:31 GMT
Author: desruisseaux
Date: Wed Oct 26 21:28:31 2016
New Revision: 1766738

URL: http://svn.apache.org/viewvc?rev=1766738&view=rev
Log:
Allow ConventionalUnit to detect when a newly created unit is equivalent to an existing one.
Add more test cases.

Modified:
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -25,6 +25,7 @@ import java.io.Serializable;
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.util.Characters;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 
@@ -65,6 +66,12 @@ abstract class AbstractUnit<Q extends Qu
     /**
      * The unit symbol, or {@code null} if this unit has no specific symbol. If {@code null},
      * then the {@link #toString()} method is responsible for creating a representation on
the fly.
+     * If non-null, this symbol should complies with the {@link UnitFormat.Style#SYMBOL}
formatting
+     * (<strong>not</strong> the UCUM format). In particular, this symbol uses
Unicode characters
+     * for arithmetic operators and superscripts, as in “m/s²”. However this symbol
should never
+     * contains the unit conversion terms. For example “km” is okay, but “1000⋅m”
is not.
+     * The intend of those rules is to make easier to analyze the symbol in methods like
+     * {@link ConventionalUnit#power(String)}.
      *
      * <p>Users can override this symbol by call to {@link UnitFormat#label(Unit, String)},
      * but such overriding applies only to the target {@code UnitFormat} instance.</p>
@@ -73,6 +80,7 @@ abstract class AbstractUnit<Q extends Qu
      * for fetching a localized name from the resource bundle.</p>
      *
      * @see #getSymbol()
+     * @see SystemUnit#alternate(String)
      */
     private final String symbol;
 
@@ -95,7 +103,7 @@ abstract class AbstractUnit<Q extends Qu
      *
      * @param  symbol  the unit symbol, or {@code null} if this unit has no specific symbol.
      * @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.
+     * @param  epsg    the EPSG code, or 0 if this unit has no EPSG code.
      */
     AbstractUnit(final String symbol, final byte scope, final short epsg) {
         this.symbol = symbol;
@@ -266,13 +274,26 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Returns {@code true} if the given Unicode code point is a valid character for a unit
symbol.
+     * Current implementation accepts only letters and subscripts, but the set of legal characters
+     * may be expanded in any future SIS version. The most important goal is to avoid confusion
with
+     * exponents.
+     *
+     * <p>Note that some units defined in the {@link Units} class break this rule.
But the hard-coded
+     * symbols in that class are known to be consistent with SI usage or with {@link UnitFormat}
work.</p>
+     */
+    static boolean isSymbolChar(final int c) {
+        return Character.isLetter(c) || Characters.isSubScript(c);
+    }
+
+    /**
      * Invoked on deserialization for returning a unique instance of {@code AbstractUnit}
if possible.
      */
     final Object readResolve() throws ObjectStreamException {
         if (symbol != null && Units.initialized) {              // Force Units class
initialization.
-            final Unit<?> exising = (Unit<?>) UnitRegistry.putIfAbsent(symbol,
this);
-            if (equals(exising)) {
-                return exising;
+            final Object existing = UnitRegistry.putIfAbsent(symbol, this);
+            if (equals(existing)) {
+                return (Unit<?>) existing;
             }
         }
         return this;

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -25,7 +25,10 @@ import javax.measure.UnconvertibleExcept
 import javax.measure.IncommensurableException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Characters;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.math.MathFunctions;
+import org.apache.sis.internal.util.Numerics;
 
 
 /**
@@ -43,6 +46,19 @@ final class ConventionalUnit<Q extends Q
     private static final long serialVersionUID = 6963634855104019466L;
 
     /**
+     * The SI prefixes form smallest to largest. Power of tens go from -24 to +24 inclusive
with a step of 3,
+     * except for the addition of -2, -1, +1, +2 and the omission of 0.
+     *
+     * @see #prefix(double)
+     */
+    private static final char[] PREFIXES = {'y','z','a','f','p','n','µ','m','c','d','㍲','h','k','M','G','T','P','E','Z','Y'};
+
+    /**
+     * The maximal power of 1000 for the prefixes in the {@link #PREFIXES} array. Note that
1000⁸ = 1E+24.
+     */
+    static final int MAX_POWER = 8;
+
+    /**
      * The base, derived or alternate units to which this {@code ConventionalUnit} is related.
      * This is called "preferred unit" in GML.
      */
@@ -56,11 +72,11 @@ final class ConventionalUnit<Q extends Q
     /**
      * Creates a new unit having the given symbol and EPSG code.
      *
-     * @param  target     the base or derived units to which this {@code ConventionalUnit}
is related.
-     * @param  toTarget   the conversion from this unit to the {@code target} unit.
-     * @param  symbol     the unit symbol, or {@code null} if this unit has no specific symbol.
-     * @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.
+     * @param  target    the base or derived units to which this {@code ConventionalUnit}
is related.
+     * @param  toTarget  the conversion from this unit to the {@code target} unit.
+     * @param  symbol    the unit symbol, or {@code null} if this unit has no specific symbol.
+     * @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) {
         super(symbol, scope, epsg);
@@ -71,12 +87,140 @@ final class ConventionalUnit<Q extends Q
     /**
      * Creates a new unit with default name and symbol for the given converter.
      */
+    @SuppressWarnings("unchecked")
     static <Q extends Quantity<Q>> AbstractUnit<Q> create(final SystemUnit<Q>
target, final UnitConverter toTarget) {
         if (toTarget.isIdentity()) {
             return target;
         }
-        // TODO: check for existing unit.
-        return new ConventionalUnit<>(target, toTarget, null, (byte) 0, (short) 0);
+        /*
+         * If the unit is a SI unit, try to create the SI symbol by the concatenation of
the SI prefix
+         * with the system unit symbol. The unit symbol are used later as a key for searching
existing
+         * unit instances.
+         */
+        String symbol = null;
+        if (target.scope == UnitRegistry.SI) {
+            final String ts = target.getSymbol();
+            if (ts != null && !ts.isEmpty() && toTarget.isLinear()) {
+                final int power = power(ts);
+                if (power != 0) {
+                    double scale = toTarget.convert(1);
+                    switch (power) {
+                        case 1:  break;
+                        case 2:  scale = Math.sqrt(scale); break;
+                        case 3:  scale = Math.cbrt(scale); break;
+                        default: scale = Math.pow(scale, 1.0/power);
+                    }
+                    final char prefix = prefix(scale);
+                    if (prefix != 0) {
+                        if (prefix == '㍲') {
+                            symbol = UnitFormat.DECA + ts;
+                        } else {
+                            symbol = prefix + ts;
+                        }
+                    }
+                }
+            }
+        }
+        /*
+         * Create the unit, but we may discard it later if an equivalent unit already exists
in the cache.
+         * The use of the cache is not only for sharing instances, but also because existing
instances may
+         * have more information.  For example instances provided by Units static constants
may contain an
+         * EPSG code, or even an alternative symbol (e.g. “hm²” will be replaced by
“ha” for hectare).
+         */
+        final ConventionalUnit<Q> unit = new ConventionalUnit<>(target, toTarget,
symbol, (byte) 0, (short) 0);
+        if (symbol != null) {
+            final Object existing = UnitRegistry.putIfAbsent(symbol, unit);
+            if (existing instanceof ConventionalUnit<?>) {
+                final ConventionalUnit<?> c = (ConventionalUnit<?>) existing;
+                if (target.equals(c.target)) {
+                    final boolean equivalent;
+                    if (toTarget instanceof LinearConverter && c.toTarget instanceof
LinearConverter) {
+                        equivalent = ((LinearConverter) toTarget).equivalent((LinearConverter)
c.toTarget);
+                    } else {
+                        equivalent = toTarget.equals(c.toTarget);   // Fallback for unknown
implementations.
+                    }
+                    if (equivalent) {
+                        return (ConventionalUnit<Q>) c;
+                    }
+                }
+            }
+        }
+        return unit;
+    }
+
+    /**
+     * Returns the positive power after the given unit symbol, or in case of doubt.
+     * For example this method returns 1 for “m” and 2 for “m²”. We parse the unit
symbol instead
+     * than the {@link SystemUnit#dimension} because we can not extract easily the power
from the
+     * product of dimensions (e.g. what is the M⋅L²∕T³ power?) Furthermore the power
will be used
+     * for choosing a symbol prefix, so we want it to be consistent with the symbol more
than the
+     * internal representation.
+     *
+     * <p>If the unit is itself a product of other units, then this method returns
the power of
+     * the first unit. For example the power of “m/s²” is 1. This means that the “k”
prefix in
+     * “km/s²” apply only to the “m” unit.</p>
+     */
+    static int power(final String symbol) {
+        final int length = symbol.length();
+        int i = 0, c;
+        do {
+            if (i >= length) return 1;              // Single symbol (no product, no exponent).
+            c = symbol.codePointAt(i);
+            i += Character.charCount(c);
+        } while (isSymbolChar(c));
+        /*
+         * At this point we found the first character which is not part of a unit symbol.
+         * We may have found the exponent as in “m²”, or we may have found an arithmetic
+         * operator like the “/” in “m/s²”. In any cases we stop here because we
want the
+         * exponent of the first symbol, not the “²” in “m/s²”.
+         */
+        if (Character.isBmpCodePoint(c)) {
+            final int p = Characters.toNormalScript((char) c) - '0';
+            if (p >= 0 && p <= 9) {
+                if (i < length) {
+                    c = symbol.codePointAt(i);
+                    if (isSymbolChar(c)) {
+                        // Exponent is immediately followed by a another unit symbol character.
+                        // We would have expected something else, like an arithmetic operator.
+                        return 0;
+                    }
+                    if (Character.isBmpCodePoint(c)) {
+                        c = Characters.toNormalScript((char) c);
+                        if (c >= '0' && c <= '9') {
+                            // Exponent on two digits. We do not expect so high power after
unit symbol.
+                            return 0;
+                        }
+                    }
+                }
+                return p;
+            }
+        }
+        return 1;
+    }
+
+    /**
+     * Returns the SI prefix for the given scale factor, or 0 if none.
+     */
+    @SuppressWarnings("null")
+    static char prefix(final double scale) {
+        final int n = Numerics.toExp10(Math.getExponent(scale)) + 1;
+        if (Math.abs(MathFunctions.pow10(n) - scale) <= Math.ulp(scale)) {
+            int i = Math.abs(n);
+            switch (i) {
+                case 0:  return 0;
+                case 1:  // Fallthrough
+                case 2:  break;
+                default: {
+                    if (i > (MAX_POWER*3) || (i % 3) != 0) {
+                        return 0;
+                    }
+                    i = i/3 + 2;
+                    break;
+                }
+            }
+            return PREFIXES[n >= 0 ? (MAX_POWER+1) + i : (MAX_POWER+2) - i];
+        }
+        return 0;
     }
 
     /**

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -292,6 +292,7 @@ final class LinearConverter extends Abst
      */
     @Override
     public double convert(final double value) {
+        // TODO: use JDK9' Math.fma(…) and verify if it solve the accuracy issue in LinearConverterTest.inverse().
         return (value * scale + offset) / divisor;
     }
 
@@ -314,8 +315,10 @@ final class LinearConverter extends Abst
                 BigDecimal offset10 = this.offset10;
                 if (scale10 == null || offset10 == null) {
                     final BigDecimal divisor = BigDecimal.valueOf(this.divisor);
-                    this.scale10  = scale10  = BigDecimal.valueOf(scale) .divide(divisor);
-                    this.offset10 = offset10 = BigDecimal.valueOf(offset).divide(divisor);
+                    scale10  = BigDecimal.valueOf(scale) .divide(divisor);
+                    offset10 = BigDecimal.valueOf(offset).divide(divisor);
+                    this.scale10  = scale10;            // Volatile fields
+                    this.offset10 = offset10;
                 }
                 value = ((BigDecimal) value).multiply(scale10).add(offset10);
             } else {
@@ -438,6 +441,16 @@ final class LinearConverter extends Abst
     }
 
     /**
+     * Returns {@code true} if the given converter perform the same conversion than this
converter,
+     * except for rounding errors.
+     */
+    boolean equivalent(final LinearConverter other) {
+        double r;
+        return Math.abs((r = scale  * other.divisor) - other.scale  * divisor) <= Math.ulp(r)
&&
+               Math.abs((r = offset * other.divisor) - other.offset * divisor) <= Math.ulp(r);
+    }
+
+    /**
      * Returns a string representation of this converter for debugging purpose.
      * This string representation may change in any future SIS release.
      * Current format is of the form "y = scale⋅x + offset".

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -69,7 +69,7 @@ final class SystemUnit<Q extends Quantit
      * @param  dimension  the unit dimension.
      * @param  symbol     the unit symbol, or {@code null} if this unit has no specific symbol.
      * @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.
+     * @param  epsg       the EPSG code, or 0 if this unit has no EPSG code.
      */
     SystemUnit(final Class<Q> quantity, final UnitDimension dimension, final String
symbol, final byte scope, final short epsg) {
         super(symbol, scope, epsg);
@@ -334,7 +334,15 @@ final class SystemUnit<Q extends Quantit
     @Override
     @SuppressWarnings("unchecked")
     public Unit<Q> alternate(final String symbol) {
-        ArgumentChecks.ensureNonNull("symbol", symbol);
+        ArgumentChecks.ensureNonEmpty("symbol", symbol);
+        for (int i=0; i < symbol.length();) {
+            final int c = symbol.codePointAt(i);
+            if (!isSymbolChar(c)) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2,
+                        "symbol", String.valueOf(Character.toChars(c))));
+            }
+            i += Character.charCount(c);
+        }
         if (symbol.equals(getSymbol())) {
             return this;
         }
@@ -345,13 +353,13 @@ final class SystemUnit<Q extends Quantit
              * in read-only mode when 'quantity' is null because we would be unable to guarantee
that
              * the parameterized type <Q> is correct.
              */
-            final Unit<?> existing = (Unit<?>) UnitRegistry.putIfAbsent(symbol,
alt);
+            final Object existing = UnitRegistry.putIfAbsent(symbol, alt);
             if (existing != null) {
-                if (existing instanceof SystemUnit<?>
-                        && ((SystemUnit<?>) existing).quantity == quantity
-                        && dimension.equals(existing.getDimension()))
-                {
-                    return (Unit<Q>) existing;
+                if (existing instanceof SystemUnit<?>) {
+                    final SystemUnit<?> unit = (SystemUnit<?>) existing;
+                    if (quantity.equals(unit.quantity) && dimension.equals(unit.dimension))
{
+                        return (SystemUnit<Q>) unit;
+                    }
                 }
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementAlreadyPresent_1,
symbol));
             }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -79,6 +79,13 @@ public class UnitFormat extends Format i
     private static final long serialVersionUID = -3064428584419360693L;
 
     /**
+     * The SI "“deca” prefix. This is the only SI prefix encoded on two letters instead
than one.
+     * It can be represented by the CJK compatibility character “㍲”, but use of those
characters
+     * is generally not recommended outside of Chinese, Japanese or Korean texts.
+     */
+    static final String DECA = "da";
+
+    /**
      * The default instance used by {@link Units#valueOf(String)} for parsing units of measurement.
      * While {@code UnitFormat} is generally not thread-safe, this particular instance is
safe if
      * we never invoke any setter method.
@@ -328,7 +335,10 @@ public class UnitFormat extends Format i
      * If the specified label is already associated to another unit, then the previous association
is discarded.
      *
      * <p>The given label must be non-empty and can not ends with a digit, since such
digit would be confused
-     * with unit power.</p>
+     * with unit power. Current implementation does not put additional restrictions. However
if the label will
+     * be used as a {@linkplain AbstractUnit#getSymbol() unit symbol} (as opposed to {@link
AbstractUnit#getName()
+     * unit name}), then we recommend to restrict the characters to {@linkplain Character#isLetter(int)
letters}
+     * and {@linkplain Characters#isSubScript(int) subscripts}.</p>
      *
      * @param  unit   the unit being labeled.
      * @param  label  the new label for the given unit.
@@ -336,7 +346,7 @@ public class UnitFormat extends Format i
      */
     @Override
     public void label(final Unit<?> unit, String label) {
-        ArgumentChecks.ensureNonNull("unit",  unit);
+        ArgumentChecks.ensureNonNull ("unit", unit);
         label = CharSequences.trimWhitespaces(label);
         ArgumentChecks.ensureNonEmpty("label", label);
         int c = Character.codePointBefore(label, label.length());
@@ -850,7 +860,7 @@ public class UnitFormat extends Format i
                 if (c != null) {
                     String symbol = unit.getSymbol();
                     if (prefix == '㍲') {
-                        symbol = "da" + symbol;
+                        symbol = DECA + symbol;
                     } else {
                         symbol = prefix + symbol;
                     }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8]
(original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8]
Wed Oct 26 21:28:31 2016
@@ -412,6 +412,17 @@ public final class Units extends Static
     public static final Unit<Time> TROPICAL_YEAR;
 
     /**
+     * The SI derived unit for frequency (Hz).
+     * One hertz is equal to one cycle per second.
+     * The unlocalized name is “hertz”.
+     *
+     * @since 0.8
+     *
+     * @see #SECOND
+     */
+    public static final Unit<Frequency> HERTZ;
+
+    /**
      * The SI derived unit for pressure (Pa).
      * One pascal is equal to 1 N/m².
      * Pressures are often used in {@linkplain org.apache.sis.referencing.crs.DefaultParametricCRS
parametric CRS}
@@ -506,6 +517,14 @@ public final class Units extends Static
     public static final Unit<Mass> KILOGRAM;
 
     /**
+     * The SI base unit for electric current (A).
+     * The unlocalized name is “ampere”.
+     *
+     * @since 0.8
+     */
+    public static final Unit<ElectricCurrent> AMPERE;
+
+    /**
      * The SI derived unit for force (N).
      * One newton is the force required to give a mass of 1 kg an acceleration of 1 m/s².
      * The unlocalized name is “newton”.
@@ -556,15 +575,20 @@ public final class Units extends Static
     public static final Unit<Temperature> CELSIUS;
 
     /**
-     * The SI derived unit for frequency (Hz).
-     * One hertz is equal to one cycle per second.
-     * The unlocalized name is “hertz”.
+     * The SI base unit for luminous intensity (cd).
+     * The unlocalized name is “candela”.
      *
      * @since 0.8
+     */
+    public static final Unit<LuminousIntensity> CANDELA;
+
+    /**
+     * The SI base unit for amount of substance (mol).
+     * The unlocalized name is “mole”.
      *
-     * @see #SECOND
+     * @since 0.8
      */
-    public static final Unit<Frequency> HERTZ;
+    public static final Unit<AmountOfSubstance> MOLE;
 
     /**
      * The base dimensionless unit for scale measurements.
@@ -650,7 +674,10 @@ public final class Units extends Static
         final UnitDimension length        = new UnitDimension('L');
         final UnitDimension mass          = new UnitDimension('M');
         final UnitDimension time          = new UnitDimension('T');
+        final UnitDimension electric      = new UnitDimension('I');
         final UnitDimension temperature   = new UnitDimension('Θ');
+        final UnitDimension amount        = new UnitDimension('N');
+        final UnitDimension luminous      = new UnitDimension('J');
         final UnitDimension area          = length.pow(2);
         final UnitDimension speed         = length.divide(time);
         final UnitDimension force         = mass.multiply(speed).divide(time);
@@ -717,16 +744,19 @@ public final class Units extends Static
         PASCAL              = Pa;
         SQUARE_METRE        = m2;
         METRES_PER_SECOND   = mps;
-        KILOGRAM            = add(Mass.class,      mass,                   "kg",   UnitRegistry.SI,
      (short) 0);
-        CUBIC_METRE         = add(Volume.class,    length.pow(3),          "m³",   UnitRegistry.SI,
      (short) 0);
-        NEWTON              = add(Force.class,     force,                  "N",    UnitRegistry.SI,
      (short) 0);
-        JOULE               = add(Energy.class,    energy,                 "J",    UnitRegistry.SI,
      (short) 0);
-        WATT                = add(Power.class,     energy.divide(time),    "W",    UnitRegistry.SI,
      (short) 0);
-        HERTZ               = add(Frequency.class, time.pow(-1),           "Hz",   UnitRegistry.SI,
      (short) 0);
-        HECTOPASCAL         = add(Pa, hecto,                               "hPa",  UnitRegistry.SI,
      (short) 0);
-        HECTARE             = add(m2,  LinearConverter.scale(10000, 1),    "ha",   UnitRegistry.ACCEPTED,
(short) 0);
-        KILOMETRES_PER_HOUR = add(mps, LinearConverter.scale(6, 100),      "km∕h", UnitRegistry.ACCEPTED,
(short) 0);
-        CELSIUS             = add(K,   LinearConverter.offset(27315, 100), "°C",   UnitRegistry.SI,
      (short) 0);
+        HERTZ               = add(Frequency.class,         time.pow(-1),           "Hz",
  UnitRegistry.SI,       (short) 0);
+        KILOGRAM            = add(Mass.class,              mass,                   "kg",
  UnitRegistry.SI,       (short) 0);
+        CUBIC_METRE         = add(Volume.class,            length.pow(3),          "m³",
  UnitRegistry.SI,       (short) 0);
+        AMPERE              = add(ElectricCurrent.class,   electric,               "A", 
  UnitRegistry.SI,       (short) 0);
+        NEWTON              = add(Force.class,             force,                  "N", 
  UnitRegistry.SI,       (short) 0);
+        JOULE               = add(Energy.class,            energy,                 "J", 
  UnitRegistry.SI,       (short) 0);
+        WATT                = add(Power.class,             energy.divide(time),    "W", 
  UnitRegistry.SI,       (short) 0);
+        CANDELA             = add(LuminousIntensity.class, luminous,               "cd",
  UnitRegistry.SI,       (short) 0);
+        MOLE                = add(AmountOfSubstance.class, amount,                 "mol",
 UnitRegistry.SI,       (short) 0);
+        HECTOPASCAL         = add(Pa,  hecto,                                      "hPa",
 UnitRegistry.SI,       (short) 0);
+        HECTARE             = add(m2,  LinearConverter.scale(10000, 1),            "ha",
  UnitRegistry.ACCEPTED, (short) 0);
+        KILOMETRES_PER_HOUR = add(mps, LinearConverter.scale(6, 100),              "km∕h",
UnitRegistry.ACCEPTED, (short) 0);
+        CELSIUS             = add(K,   LinearConverter.offset(27315, 100),         "°C",
  UnitRegistry.SI,       (short) 0);
         /*
          * All Unit<Dimensionless>
          */
@@ -745,6 +775,7 @@ public final class Units extends Static
         UnitRegistry.alias(CELSIUS,     "℃");
         UnitRegistry.alias(CELSIUS,   "Cel");
         UnitRegistry.alias(GRAD,      "gon");
+        UnitRegistry.alias(HECTARE,   "hm²");
 
         initialized = true;
     }

Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
[ISO-8859-1] Wed Oct 26 21:28:31 2016
@@ -1,5 +1,7 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.
+A=ampere
 a=year
+cd=candela
 cm=centimetre
 d=day
 ft=foot
@@ -21,6 +23,7 @@ m³=cubic metre
 mi=statute mile
 min=minute
 mm=millimetre
+mol=mole
 ms=millisecond
 N=newton
 nm=nanometre

Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
[ISO-8859-1] Wed Oct 26 21:28:31 2016
@@ -1,4 +1,5 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.
+A=ampère
 a=année
 cm=centimètre
 d=jour

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -16,16 +16,21 @@
  */
 package org.apache.sis.measure;
 
+import java.lang.reflect.Field;
 import javax.measure.Unit;
+import javax.measure.UnitConverter;
 import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
- * Tests the {@link ConventionalUnit} class.
+ * Tests the {@link ConventionalUnit} class. This class tests also the {@link SystemUnit#multiply(double)}
and
+ * {@link SystemUnit#divide(double)} methods since they are used for creating {@code ConventionalUnit}
instances,
+ * but those methods just delegate to {@link ConventionalUnit#create(SystemUnit, UnitConverter)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
@@ -45,7 +50,7 @@ public final strictfp class Conventional
     static void verify(final Unit<?> system, final Unit<?> unit, final String
symbol, final double scale) {
         assertSame  ("getSystemUnit()", system, unit.getSystemUnit());
         assertEquals("getSymbol()",     symbol, unit.getSymbol());
-        assertEquals("UnitConverter", scale, Units.toStandardUnit(unit), STRICT);
+        assertEquals("UnitConverter",   scale,  Units.toStandardUnit(unit), STRICT);
     }
 
     /**
@@ -70,4 +75,137 @@ public final strictfp class Conventional
         verify(Units.UNITY,             Units.PERCENT,                "%",  1E-2);
         verify(Units.UNITY,             Units.PPM,                  "ppm",  1E-6);
     }
+
+    /**
+     * Tests {@link ConventionalUnit#prefix(double)}.
+     */
+    @Test
+    public void testPrefix() {
+        assertEquals( 0 , ConventionalUnit.prefix(1E-27));
+        assertEquals( 0 , ConventionalUnit.prefix(1E-25));
+        assertEquals('y', ConventionalUnit.prefix(1E-24));
+        assertEquals( 0 , ConventionalUnit.prefix(1E-23));
+        assertEquals('n', ConventionalUnit.prefix(1E-09));
+        assertEquals( 0 , ConventionalUnit.prefix(1E-08));
+        assertEquals( 0 , ConventionalUnit.prefix(1E-04));
+        assertEquals('m', ConventionalUnit.prefix(1E-03));
+        assertEquals('c', ConventionalUnit.prefix(1E-02));
+        assertEquals('d', ConventionalUnit.prefix(1E-01));
+        assertEquals( 0 , ConventionalUnit.prefix(    1));
+        assertEquals( 0 , ConventionalUnit.prefix(    0));
+        assertEquals( 0 , ConventionalUnit.prefix(  -10));
+        assertEquals('㍲', ConventionalUnit.prefix(   10));
+        assertEquals('h', ConventionalUnit.prefix(  100));
+        assertEquals('k', ConventionalUnit.prefix( 1000));
+        assertEquals( 0 , ConventionalUnit.prefix(1E+04));
+        assertEquals('G', ConventionalUnit.prefix(1E+09));
+        assertEquals('Y', ConventionalUnit.prefix(1E+24));
+        assertEquals( 0 , ConventionalUnit.prefix(1E+25));
+        assertEquals( 0 , ConventionalUnit.prefix(1E+27));
+        assertEquals( 0 , ConventionalUnit.prefix(1E+25));
+    }
+
+    /**
+     * Tests {@link ConventionalUnit#power(String)}.
+     */
+    @Test
+    public void testPower() {
+        assertEquals("m",    1, ConventionalUnit.power("m"));
+        assertEquals("m²",   2, ConventionalUnit.power("m²"));
+        assertEquals("m2",   2, ConventionalUnit.power("m2"));
+        assertEquals("m₂",   1, ConventionalUnit.power("m₂"));      // Because the "2"
is in indice.
+        assertEquals("m³",   3, ConventionalUnit.power("m³"));
+        assertEquals("m/s²", 1, ConventionalUnit.power("m/s²"));
+        assertEquals("km/h", 1, ConventionalUnit.power("km/h"));
+        assertEquals("m³/s", 3, ConventionalUnit.power("m³/s"));
+        assertEquals("m³s",  0, ConventionalUnit.power("m³s"));     // Illegal symbol.
+    }
+
+    /**
+     * Ensures that the characters in the {@link ConventionalUnit#PREFIXES} array match
+     * the prefixes recognized by {@link LinearConverter#forPrefix(char)}.
+     *
+     * @throws ReflectiveOperationException if this test can not access the private fields
of {@link LinearConverter}.
+     *
+     * @see LinearConverterTest#verifyPrefixes()
+     */
+    @Test
+    @DependsOnMethod("testPrefix")
+    public void verifyPrefixes() throws ReflectiveOperationException {
+        Field f = ConventionalUnit.class.getDeclaredField("PREFIXES");
+        f.setAccessible(true);
+        double previousScale = StrictMath.pow(1000, -(ConventionalUnit.MAX_POWER + 1));
+        for (final char prefix : (char[]) f.get(null)) {
+            final LinearConverter lc = LinearConverter.forPrefix(prefix);
+            final String asString = String.valueOf(prefix);
+            assertNotNull(asString, lc);
+            /*
+             * Ratio of previous scale with current scale shall be a power of 10.
+             */
+            final double scale = lc.derivative(0);
+            final double power = StrictMath.log10(scale / previousScale);
+            assertTrue  (asString,    power >= 1);
+            assertEquals(asString, 0, power % 1, STRICT);
+            /*
+             * At this point we got the LinearConverter to use for the test,
+             * and we know the expected prefix. Verify that we get that value.
+             */
+            assertEquals("ConventionalUnit.prefix(double)", asString, String.valueOf(ConventionalUnit.prefix(scale)));
+            previousScale = scale;
+        }
+    }
+
+    /**
+     * Tests {@link SystemUnit#multiply(double)} and {@link SystemUnit#divide(double)}.
+     * Both are implemented by calls to {@link SystemUnit#transform(UnitConverter)}.
+     */
+    @Test
+    @DependsOnMethod({"verifyPrefixes","testPower"})
+    public void testTransformSystemUnit() {
+        assertSame(Units.METRE,      Units.METRE.multiply(   1));
+        assertSame(Units.KILOMETRE,  Units.METRE.multiply(1000));
+        assertSame(Units.MILLIMETRE, Units.METRE.divide  (1000));
+        assertSame(Units.CENTIMETRE, Units.METRE.divide  ( 100));
+        assertSame(Units.NANOMETRE,  Units.METRE.divide  (1E+9));
+        assertSame(Units.NANOMETRE,  Units.METRE.multiply(1E-9));
+        verify    (Units.METRE,      Units.METRE.multiply( 100), "hm", 100);
+        verify    (Units.METRE,      Units.METRE.multiply(  10), "dam", 10);
+
+        assertSame(Units.METRES_PER_SECOND, Units.METRES_PER_SECOND.multiply(   1));
+        verify    (Units.METRES_PER_SECOND, Units.METRES_PER_SECOND.multiply(1000), "km∕s",
1E+3);
+        verify    (Units.METRES_PER_SECOND, Units.METRES_PER_SECOND.divide  (1000), "mm∕s",
1E-3);
+
+        assertSame(Units.SQUARE_METRE, Units.SQUARE_METRE.multiply(   1));
+        assertSame(Units.HECTARE,      Units.SQUARE_METRE.multiply(1E+4));
+        assertSame(Units.CUBIC_METRE,  Units.CUBIC_METRE .multiply(   1));
+        verify    (Units.CUBIC_METRE,  Units.CUBIC_METRE .multiply(1E+9), "km³", 1E+9);
+        verify    (Units.CUBIC_METRE,  Units.CUBIC_METRE .divide  (1E+9), "mm³", 1E-9);
+    }
+
+    /**
+     * Tests {@link ConventionalUnit#multiply(double)} and {@link ConventionalUnit#divide(double)}.
+     * Both are implemented by calls to {@link ConventionalUnit#transform(UnitConverter)}.
+     */
+    @Test
+    @DependsOnMethod("testTransformSystemUnit")
+    public void testTransformConventionalUnit() {
+        assertSame(Units.MILLIMETRE, Units.MILLIMETRE.multiply(   1));
+        assertSame(Units.CENTIMETRE, Units.MILLIMETRE.multiply(  10));
+        assertSame(Units.MILLIMETRE, Units.CENTIMETRE.divide  (  10));
+        assertSame(Units.MILLIMETRE, Units.CENTIMETRE.multiply( 0.1));
+        assertSame(Units.KILOMETRE,  Units.MILLIMETRE.multiply(1E+6));
+        assertSame(Units.NANOMETRE,  Units.KILOMETRE .divide  (1E+12));
+        assertSame(Units.NANOMETRE,  Units.KILOMETRE .multiply(1E-12));
+
+        verify(Units.SQUARE_METRE, Units.HECTARE.divide(1E+10), "mm²", 1E-6);
+    }
+
+    /**
+     * Serializes some units, deserializes them and verifies that we get the same instance.
+     */
+    @Test
+    public void testSerialization() {
+        assertSame(Units.KILOMETRE, assertSerializedEquals(Units.KILOMETRE));
+        assertSame(Units.HECTARE,   assertSerializedEquals(Units.HECTARE));
+    }
 }

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -65,6 +65,8 @@ public final strictfp class LinearConver
      * and that {@link LinearConverter#POWERS} has the same length.
      *
      * @throws ReflectiveOperationException if this test can not access the private fields
of {@link LinearConverter}.
+     *
+     * @see ConventionalUnitTest#verifyPrefixes()
      */
     @Test
     public void verifyPrefixes() throws ReflectiveOperationException {
@@ -200,6 +202,7 @@ public final strictfp class LinearConver
         c = LinearConverter.offset(27315, 100);
         inv = (LinearConverter) c.inverse();
         assertEquals(12.3, c.convert(inv.convert(12.3)), 1E-13);
+        // TODO: use JDK9' Math.fma(…) in LinearConverter.convert(double) and verify if
it solve the accuracy issue.
     }
 
     /**

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java?rev=1766738&r1=1766737&r2=1766738&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
[UTF-8] Wed Oct 26 21:28:31 2016
@@ -81,6 +81,7 @@ public final strictfp class UnitFormatTe
         verify(declared, "DAY",                 "T",        "d",     Units.DAY);
         verify(declared, "WEEK",                "T",        "wk",    Units.WEEK);
         verify(declared, "TROPICAL_YEAR",       "T",        "a",     Units.TROPICAL_YEAR);
+        verify(declared, "HERTZ",               "∕T",       "Hz",    Units.HERTZ);
         verify(declared, "PASCAL",              "M∕(L⋅T²)", "Pa",    Units.PASCAL);
         verify(declared, "HECTOPASCAL",         "M∕(L⋅T²)", "hPa",   Units.HECTOPASCAL);
         verify(declared, "HECTARE",             "L²",       "ha",    Units.HECTARE);
@@ -89,12 +90,14 @@ public final strictfp class UnitFormatTe
         verify(declared, "METRES_PER_SECOND",   "L∕T",      "m∕s",   Units.METRES_PER_SECOND);
         verify(declared, "KILOMETRES_PER_HOUR", "L∕T",      "km∕h",  Units.KILOMETRES_PER_HOUR);
         verify(declared, "KILOGRAM",            "M",        "kg",    Units.KILOGRAM);
+        verify(declared, "AMPERE",              "I",        "A",     Units.AMPERE);
         verify(declared, "NEWTON",              "M⋅L∕T²",   "N",     Units.NEWTON);
         verify(declared, "JOULE",               "M⋅L²∕T²",  "J",     Units.JOULE);
         verify(declared, "WATT",                "M⋅L²∕T³",  "W",     Units.WATT);
         verify(declared, "KELVIN",              "Θ",        "K",     Units.KELVIN);
         verify(declared, "CELSIUS",             "Θ",        "°C",    Units.CELSIUS);
-        verify(declared, "HERTZ",               "∕T",       "Hz",    Units.HERTZ);
+        verify(declared, "CANDELA",             "J",        "cd",    Units.CANDELA);
+        verify(declared, "MOLE",                "N",        "mol",   Units.MOLE);
         verify(declared, "UNITY",               "",         "",      Units.UNITY);
         verify(declared, "PERCENT",             "",         "%",     Units.PERCENT);
         verify(declared, "PPM",                 "",         "ppm",   Units.PPM);




Mime
View raw message