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");