in endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/UnitFormat.java [624:783]
public Appendable format(final Unit<?> unit, final Appendable toAppendTo) throws IOException {
ArgumentChecks.ensureNonNull("unit", unit);
ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
/*
* Choice 1: label specified by a call to label(Unit, String).
*/
{
final String label = unitToLabel.get(unit);
if (label != null) {
return toAppendTo.append(label);
}
}
/*
* Choice 2: value specified by Unit.getName(). We skip this check if the given Unit is an instance
* implemented by Apache SIS because AbstractUnit.getName() delegates to the same resource bundle
* than the one used by this block. We are better to use the resource bundle of the UnitFormat both
* for performance reasons and because the locale may not be the same.
*/
if (style == Style.NAME) {
if (!(unit instanceof AbstractUnit)) {
final String label = unit.getName();
if (label != null) {
return toAppendTo.append(label);
}
} else {
String label = unit.getSymbol();
if (label != null) {
if (label.isEmpty()) {
label = UNITY;
}
// Following is not thread-safe, but it is okay since we do not use INSTANCE for unit names.
final ResourceBundle names = symbolToName();
try {
label = names.getString(label);
} catch (MissingResourceException e) {
Logging.ignorableException(AbstractUnit.LOGGER, UnitFormat.class, "format", e);
// Name not found; use the symbol as a fallback.
}
return toAppendTo.append(label);
}
}
}
/*
* Choice 3: if the unit has a specific symbol, appends that symbol.
* Apache SIS implementation uses Unicode characters in the symbol, which are not valid for UCUM.
* But Styme.UCUM.appendSymbol(…) performs required replacements.
*/
{
final String symbol = unit.getSymbol();
if (symbol != null) {
return style.appendSymbol(toAppendTo, symbol);
}
}
/*
* Choice 4: if all the above failed, fallback on a symbol created from the base units and their power.
* Note that this may produce more verbose symbols than needed because derived units like Volt or Watt
* are decomposed into their base SI units. The scale factor will be inserted before the unit components,
* e.g. "30⋅m∕s". Note that a scale factor relative to system unit may not be what we want if the unit
* contains "kg", since it block us from using SI prefixes. But in many cases (not all), a symbol will
* have been created by SystemUnit.transform(…), in which case "Choice 3" above would have been executed.
*/
final Unit<?> unscaled = unit.getSystemUnit();
@SuppressWarnings("unchecked") // Both `unit` and `unscaled` are `Unit<Q>`.
final double scale = AbstractConverter.scale(unit.getConverterTo((Unit) unscaled));
if (Double.isNaN(scale)) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.NonRatioUnit_1,
"?⋅" + Style.OPEN + unscaled + Style.CLOSE));
}
/*
* In addition of the scale, we will need to know:
*
* - The components (for example "m" and "s" in "m∕s").
* - Whether we have at least one component on the left side of "∕" operation.
* Used for determining if we should prepend "1" before the "∕" symbol.
* - If there is exactly one component on the left side of "∕" and that component
* is prefixable, the power raising that component. Used for choosing a prefix.
*/
int prefixPower = 0;
boolean hasNumerator = false;
final Map<? extends Unit<?>, ? extends Number> components;
if (unscaled instanceof AbstractUnit<?>) {
// In Apache SIS implementation, power may be fractional.
final Map<SystemUnit<?>, Fraction> c = ((AbstractUnit<?>) unscaled).getBaseSystemUnits();
components = c;
for (final Map.Entry<SystemUnit<?>, Fraction> e : c.entrySet()) {
final Fraction power = e.getValue();
if (power.signum() > 0) {
hasNumerator = true;
if (prefixPower == 0 && power.denominator == 1 && e.getKey().isPrefixable()) {
prefixPower = power.numerator;
} else {
prefixPower = 0;
break;
}
}
}
} else {
// Fallback for foreigner implementations (power restricted to integer).
Map<? extends Unit<?>, Integer> c = unscaled.getBaseUnits();
if (c == null) c = Map.of(unit, 1);
components = c;
for (final Map.Entry<? extends Unit<?>, Integer> e : c.entrySet()) {
final int power = e.getValue();
if (power > 0) {
hasNumerator = true;
if (prefixPower == 0 && AbstractUnit.isPrefixable(e.getKey())) {
prefixPower = power;
} else {
prefixPower = 0;
break;
}
}
}
}
/*
* Append the scale factor. If we can use a prefix (e.g. "km" instead of "1000⋅m"), we will do that.
* Otherwise if the scale is a power of 10 and we are allowed to use Unicode symbols, we will write
* for example 10⁵⋅m instead of 100000⋅m. If the scale is not a power of 10, or if we are requested
* to format UCUM symbol, then we fallback on the usual `Double.toString(double)` representation.
*/
if (scale != 1) {
final char prefix = Prefixes.symbol(scale, prefixPower);
if (prefix != 0) {
toAppendTo.append(Prefixes.concat(prefix, ""));
} else {
boolean asPowerOf10 = (style != Style.UCUM);
if (asPowerOf10) {
double power = Math.log10(scale);
asPowerOf10 = AbstractConverter.epsilonEquals(power, power = Math.round(power));
if (asPowerOf10) {
toAppendTo.append("10");
final String text = Integer.toString((int) power);
for (int i=0; i<text.length(); i++) {
toAppendTo.append(Characters.toSuperScript(text.charAt(i)));
}
}
}
if (!asPowerOf10) {
final String text = Double.toString(scale);
int length = text.length();
if (text.endsWith(".0")) length -= 2;
toAppendTo.append(text, 0, length);
}
/*
* The `formatComponents` method appends division symbol only, no multiplication symbol.
* If we have formatted a scale factor and there is at least one component to multiply,
* we need to append the multiplication symbol ourselves. Note that `formatComponents`
* put numerators before denominators, so we are sure that the first term after the
* multiplication symbol is a numerator.
*/
if (hasNumerator) {
toAppendTo.append(style.multiply);
}
}
} else if (!hasNumerator) {
toAppendTo.append('1');
}
formatComponents(components, style, toAppendTo);
return toAppendTo;
}