This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 6842fe1  Arithmetic operation Units.MILLIMETRE.divide(Units.HOUR) 
should produce a unit with symbol "mm/h".
6842fe1 is described below

commit 6842fe1343cb4c4e471fb112e321f23caf08d822
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jan 11 12:24:22 2019 +0100

    Arithmetic operation Units.MILLIMETRE.divide(Units.HOUR) should produce a 
unit with symbol "mm/h".
---
 .../java/org/apache/sis/measure/AbstractUnit.java  | 139 ++++++++++++++++++---
 .../org/apache/sis/measure/ConventionalUnit.java   |   6 +-
 .../java/org/apache/sis/measure/SystemUnit.java    |  79 +-----------
 .../org/apache/sis/measure/UnitFormatTest.java     |  15 ++-
 4 files changed, 141 insertions(+), 98 deletions(-)

diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java 
b/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
index 7f25a48..8892e85 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
@@ -73,6 +73,14 @@ abstract class AbstractUnit<Q extends Quantity<Q>> 
implements Unit<Q>, LenientCo
     static final char MULTIPLY = '⋅', DIVIDE = '∕';
 
     /**
+     * Maximum number of multiplications or divisions in the symbol of the 
first unit
+     * for allowing the use of that symbol for inferring a new symbol.
+     *
+     * @see #isValidSymbol(String, boolean, boolean)
+     */
+    private static final int MAX_OPERATIONS_IN_SYMBOL = 1;
+
+    /**
      * 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
@@ -145,6 +153,120 @@ abstract class AbstractUnit<Q extends Quantity<Q>> 
implements Unit<Q>, LenientCo
     }
 
     /**
+     * Returns {@code true} if the given Unicode code point is a valid 
character for a unit symbol.
+     * Current implementation accepts letters, subscripts and the degree sign, 
but the set of legal
+     * characters may be expanded in any future SIS version (however it should 
never allow spaces).
+     * The goal is to avoid confusion with exponents and to detect where a 
unit symbol ends.
+     *
+     * <p>Space characters must be excluded from the set of legal characters 
because allowing them
+     * would make harder for {@link UnitFormat} to detect correctly where a 
unit symbol ends.</p>
+     *
+     * <p>Note that some units defined in the {@link Units} class break this 
rule. In particular,
+     * some of those units contains superscripts or division sign. 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) || 
"°'′’\"″%‰‱-_".indexOf(c) >= 0;
+    }
+
+    /**
+     * Returns {@code true} if the given unit symbol is valid.
+     *
+     * @param  symbol          the symbol to verify for validity.
+     * @param  allowExponents  whether to accept also exponent characters.
+     * @param  allowMultiply   whether to accept a few multiplications.
+     * @return {@code true} if the given symbol is valid.
+     */
+    private static boolean isValidSymbol(final String symbol, final boolean 
allowExponents, final boolean allowMultiply) {
+        return invalidCharForSymbol(symbol, allowMultiply ? 
MAX_OPERATIONS_IN_SYMBOL : 0, allowExponents) == -1;
+    }
+
+    /**
+     * If the given symbol contains an invalid character for a unit symbol, 
returns the character code point.
+     * Otherwise if the given symbol is null or empty, returns -2. Otherwise 
(the symbol is valid) returns -1.
+     *
+     * <p>The check for valid symbols can be relaxed, for example when 
building a new symbol from existing units.
+     * For example we may want to accept "W" and "m²" as valid symbols for 
deriving "W∕m²" without being rejected
+     * because of the "²" in "m²". We do not want to relax too much however, 
because a long sequence of arithmetic
+     * operations would result in a long and maybe meaningless unit symbol, 
while declaring "no symbol" would allow
+     * {@link UnitFormat} to create a new one from the base units. The 
criterion for accepting a symbol or not (for
+     * example how many multiplications) is arbitrary.</p>
+     *
+     * @param  symbol          the symbol to verify for invalid characters.
+     * @param  maxMultiply     maximal number of multiplication symbol to 
accept.
+     * @param  allowExponents  whether to accept also exponent characters.
+     * @return Unicode code point of the invalid character, or a negative 
value.
+     */
+    static int invalidCharForSymbol(final String symbol, int maxMultiply, 
final boolean allowExponents) {
+        if (symbol == null || symbol.isEmpty()) {
+            return -2;
+        }
+        for (int i=0; i < symbol.length();) {
+            final int c = symbol.codePointAt(i);
+            if (!isSymbolChar(c)) {
+                if (c == MULTIPLY) {
+                    if (--maxMultiply < 0) return c;
+                } else if (!allowExponents || !Characters.isSuperScript(c)) {
+                    return c;
+                }
+            }
+            i += Character.charCount(c);
+        }
+        return -1;
+    }
+
+    /**
+     * Infers a symbol for a unit resulting from an arithmetic operation 
between two units.
+     * The left operand is {@code this} unit and the right operand is the 
{@code other} unit.
+     *
+     * @param  operation  {@link #MULTIPLY}, {@link #DIVIDE} or an exponent.
+     * @param  other      the left operand used in the operation, or {@code 
null} if {@code operation} is an exponent.
+     * @return the symbol for the operation result, or {@code null} if no 
suggestion.
+     */
+    @SuppressWarnings("fallthrough")
+    final String inferSymbol(final char operation, final Unit<?> other) {
+        String  ts             = symbol;
+        boolean inverse        = false;
+        boolean allowExponents = false;
+        switch (operation) {
+            case DIVIDE:   inverse = (ts != null && ts.isEmpty());      // 
Fall through
+            case MULTIPLY: allowExponents = true; break;
+        }
+        if (inverse || isValidSymbol(ts, allowExponents, allowExponents)) {
+            if (other != null) {
+                final String os = other.getSymbol();
+                if (isValidSymbol(os, allowExponents, false)) {
+                    if (inverse) ts = SystemUnit.ONE;
+                    return (ts + operation + os).intern();
+                }
+            } else if (!allowExponents) {
+                assert Characters.isSuperScript(operation) : operation;
+                return (ts + operation).intern();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * If the {@code result} unit is a {@link ConventionalUnit} with no 
symbol, tries to infer a symbol for it.
+     * Otherwise returns {@code result} unchanged.
+     *
+     * @param  result     the result of an arithmetic operation.
+     * @param  operation  {@link #MULTIPLY}, {@link #DIVIDE} or an exponent.
+     * @param  other      the left operand used in the operation, or {@code 
null} if {@code operation} is an exponent.
+     * @return {@code result} or an equivalent unit augmented with a symbol.
+     */
+    final <R extends Quantity<R>> Unit<R> inferSymbol(Unit<R> result, final 
char operation, final Unit<?> other) {
+        if (result instanceof ConventionalUnit<?> && result.getSymbol() == 
null) {
+            final String symbol = inferSymbol(operation, other);
+            if (symbol != null) {
+                result = ((ConventionalUnit<R>) result).forSymbol(symbol);
+            }
+        }
+        return result;
+    }
+
+    /**
      * 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.
      *
@@ -396,23 +518,6 @@ abstract class AbstractUnit<Q extends Quantity<Q>> 
implements Unit<Q>, LenientCo
     }
 
     /**
-     * Returns {@code true} if the given Unicode code point is a valid 
character for a unit symbol.
-     * Current implementation accepts letters, subscripts and the degree sign, 
but the set of legal
-     * characters may be expanded in any future SIS version (however it should 
never allow spaces).
-     * The goal is to avoid confusion with exponents and to detect where a 
unit symbol ends.
-     *
-     * <p>Space characters must be excluded from the set of legal characters 
because allowing them
-     * would make harder for {@link UnitFormat} to detect correctly where a 
unit symbol ends.</p>
-     *
-     * <p>Note that some units defined in the {@link Units} class break this 
rule. In particular,
-     * some of those units contains superscripts or division sign. 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) || 
"°'′’\"″%‰‱-_".indexOf(c) >= 0;
-    }
-
-    /**
      * Invoked on deserialization for returning a unique instance of {@code 
AbstractUnit} if possible.
      */
     final Object readResolve() throws ObjectStreamException {
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java 
b/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
index a0f3e9f..603a4ba 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
@@ -400,7 +400,7 @@ final class ConventionalUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> {
     public Unit<?> multiply(final Unit<?> multiplier) {
         if (multiplier == this) return pow(2);                      // For 
formating e.g. "mi²".
         ensureRatioScale();
-        return target.multiply(multiplier).transform(toTarget);
+        return inferSymbol(target.multiply(multiplier).transform(toTarget), 
MULTIPLY, multiplier);
     }
 
     /**
@@ -412,7 +412,7 @@ final class ConventionalUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> {
     @Override
     public Unit<?> divide(final Unit<?> divisor) {
         ensureRatioScale();
-        return target.divide(divisor).transform(toTarget);
+        return inferSymbol(target.divide(divisor).transform(toTarget), DIVIDE, 
divisor);
     }
 
     /**
@@ -424,7 +424,7 @@ final class ConventionalUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> {
     @Override
     public Unit<?> pow(final int n) {
         ensureRatioScale();
-        return applyConversion(target.pow(n), n, false);
+        return inferSymbol(applyConversion(target.pow(n), n, false), 'ⁿ', 
null);
     }
 
     /**
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java 
b/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
index d46cdf6..d7caac0 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
@@ -62,12 +62,6 @@ final class SystemUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> implements
     static final String ONE = "1";
 
     /**
-     * Maximum number of multiplications or divisions in the symbol of the 
first unit
-     * for allowing the use of that symbol for inferring a new symbol.
-     */
-    private static final int MAX_OPERATIONS_IN_SYMBOL = 1;
-
-    /**
      * The type of quantity that uses this unit, or {@code null} if unknown.
      */
     final Class<Q> quantity;
@@ -150,18 +144,7 @@ final class SystemUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> implements
          */
         String symbol = null;
         if (operation != 0) {
-            final boolean allowExponents = (operation == MULTIPLY || operation 
== DIVIDE);
-            if (inverse || isValidSymbol(ts, allowExponents ? 
MAX_OPERATIONS_IN_SYMBOL : 0, allowExponents)) {
-                if (other != null) {
-                    final String os = other.getSymbol();
-                    if (isValidSymbol(os, 0, allowExponents)) {
-                        if (inverse) ts = ONE;
-                        symbol = (ts + operation + os).intern();
-                    }
-                } else if (!allowExponents) {
-                    symbol = (ts + operation).intern();             // The 
operation is an exponent.
-                }
-            }
+            symbol = inferSymbol(operation, other);
         }
         /*
          * Performs now the check that we did not performed at the
@@ -188,52 +171,6 @@ final class SystemUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> implements
     }
 
     /**
-     * Returns {@code true} if the given unit symbol is valid.
-     *
-     * @param  symbol          the symbol to verify for validity.
-     * @param  maxMultiply     maximal number of multiplication symbol to 
accept.
-     * @param  allowExponents  whether to accept also exponent characters.
-     * @return {@code true} if the given symbol is valid.
-     */
-    private static boolean isValidSymbol(final String symbol, int maxMultiply, 
final boolean allowExponents) {
-        return invalidCharForSymbol(symbol, maxMultiply, allowExponents) == -1;
-    }
-
-    /**
-     * If the given symbol contains an invalid character for a unit symbol, 
returns the character code point.
-     * Otherwise if the given symbol is null or empty, returns -2. Otherwise 
(the symbol is valid) returns -1.
-     *
-     * <p>The check for valid symbols can be relaxed, for example when 
building a new symbol from existing units.
-     * For example we may want to accept "W" and "m²" as valid symbols for 
deriving "W∕m²" without being rejected
-     * because of the "²" in "m²". We do not want to relax too much however, 
because a long sequence of arithmetic
-     * operations would result in a long and maybe meaningless unit symbol, 
while declaring "no symbol" would allow
-     * {@link UnitFormat} to create a new one from the base units. The 
criterion for accepting a symbol or not (for
-     * example how many multiplications) is arbitrary.</p>
-     *
-     * @param  symbol          the symbol to verify for invalid characters.
-     * @param  maxMultiply     maximal number of multiplication symbol to 
accept.
-     * @param  allowExponents  whether to accept also exponent characters.
-     * @return Unicode code point of the invalid character, or a negative 
value.
-     */
-    private static int invalidCharForSymbol(final String symbol, int 
maxMultiply, final boolean allowExponents) {
-        if (symbol == null || symbol.isEmpty()) {
-            return -2;
-        }
-        for (int i=0; i < symbol.length();) {
-            final int c = symbol.codePointAt(i);
-            if (!isSymbolChar(c)) {
-                if (c == MULTIPLY) {
-                    if (--maxMultiply < 0) return c;
-                } else if (!allowExponents || !Characters.isSuperScript(c)) {
-                    return c;
-                }
-            }
-            i += Character.charCount(c);
-        }
-        return -1;
-    }
-
-    /**
      * Returns the dimension of this unit.
      * Two units {@code u1} and {@code u2} are {@linkplain #isCompatible(Unit) 
compatible}
      * if and only if {@code u1.getDimension().equals(u2.getDimension())}.
@@ -541,7 +478,7 @@ final class SystemUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> implements
      *
      * @param  inverse  wether to use the inverse of {@code other}.
      */
-    private <T extends Quantity<T>> Unit<?> product(final Unit<T> other, 
boolean inverse) {
+    private <T extends Quantity<T>> Unit<?> product(final Unit<T> other, final 
boolean inverse) {
         final Unit<T> intermediate = other.getSystemUnit();
         final Dimension dim = intermediate.getDimension();
         final UnitDimension newDimension;
@@ -567,17 +504,7 @@ final class SystemUnit<Q extends Quantity<Q>> extends 
AbstractUnit<Q> implements
                  * If the system unit product is an Apache SIS implementation, 
try to infer a unit symbol
                  * to be given to our customized 'transform' method. Otherwise 
fallback on standard API.
                  */
-                if (result.getSymbol() == null && result instanceof 
ConventionalUnit<?>) {
-                    String ts = getSymbol();
-                    inverse &= (ts != null && ts.isEmpty());
-                    if (inverse || isValidSymbol(ts, MAX_OPERATIONS_IN_SYMBOL, 
true)) {
-                        final String os = other.getSymbol();
-                        if (isValidSymbol(os, 0, true)) {
-                            if (inverse) ts = ONE;
-                            result = ((ConventionalUnit<?>) 
result).forSymbol((ts + operation + os).intern());
-                        }
-                    }
-                }
+                result = inferSymbol(result, operation, other);
             }
         }
         return result;
diff --git 
a/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java 
b/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
index c781c45..6ed271e 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
@@ -305,6 +305,17 @@ public final strictfp class UnitFormatTest extends 
TestCase {
     }
 
     /**
+     * Tests formatting ratio of units.
+     */
+    @Test
+    public void testFormatRatio() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        f.setStyle(UnitFormat.Style.SYMBOL);
+        assertEquals( "m∕h", f.format(Units.METRE.divide(Units.HOUR)));
+        assertEquals("mm∕h", f.format(Units.MILLIMETRE.divide(Units.HOUR)));
+    }
+
+    /**
      * Tests formatting of some more unusual units. The units tested by this 
method are artificial
      * and somewhat convolved. The intent is to verify that unit formatting is 
still robust.
      */
@@ -328,7 +339,7 @@ public final strictfp class UnitFormatTest extends TestCase 
{
         final UnitFormat f = new UnitFormat(Locale.UK);
         assertEquals(   "1∕m²", 
f.format(Units.UNITY.divide(Units.SQUARE_METRE)));
         assertEquals("10⁻²∕m²", 
f.format(Units.UNITY.divide(100).divide(Units.SQUARE_METRE)));
-        assertEquals("10⁻²∕m²", 
f.format(Units.PERCENT.divide(Units.SQUARE_METRE)));
+        assertEquals("%∕m²",    
f.format(Units.PERCENT.divide(Units.SQUARE_METRE)));
     }
 
     /**
@@ -686,6 +697,7 @@ public final strictfp class UnitFormatTest extends TestCase 
{
         roundtrip(f, "rad.s-1",          "rad∕s");
         roundtrip(f, "(m2.s)^-1",        "1∕(m²⋅s)");
         roundtrip(f, "(m2.s)-1",         "1∕(m²⋅s)");
+        roundtrip(f, "cm/day",           "cm∕d");
     }
 
     /**
@@ -719,7 +731,6 @@ public final strictfp class UnitFormatTest extends TestCase 
{
         final UnitFormat f = new UnitFormat(Locale.UK);
         roundtrip(f, "kg.kg-1.m.s-1",    "m∕s");
         roundtrip(f, "(m2.s.sr)-1",      "1∕(m²⋅s)");
-        roundtrip(f, "cm/day",           "1.1574074074074074E-7⋅m∕s");
         roundtrip(f, "m-2.s.rad-1",      "s∕m²");
         roundtrip(f, "kg.kg-1.s-1",      "Hz");
         roundtrip(f, "kg/kg*kg/kg",      "kg∕kg");

Reply via email to