public Appendable format()

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;
    }