All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.impl.units.UnitsRouter Maven / Gradle / Ivy

Go to download

International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support

The newest version!
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.impl.units;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import com.ibm.icu.impl.IllegalIcuArgumentException;
import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.number.Precision;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;

/**
 * {@code UnitsRouter} responsible for converting from a single unit (such as {@code meter} or
 * {@code meter-per-second}) to one of the complex units based on the limits.
 * For example:
 * if the input is {@code meter} and the output as following
 * {{@code foot+inch}, limit: 3.0}
 * {{@code inch}     , limit: no value (-inf)}
 * Thus means if the input in {@code meter} is greater than or equal to {@code 3.0 feet},
 * the output will be in {@code foot+inch}, otherwise, the output will be in {@code inch}.
 * 

* NOTE: * the output units and their limits MUST BE in order, for example, if the output units, from the * previous example, are the following: * {{@code inch} , limit: no value (-inf)} * {{@code foot+inch}, limit: 3.0} * IN THIS CASE THE OUTPUT WILL BE ALWAYS IN {@code inch}. *

* NOTE: * the output units and their limits will be extracted from the units preferences database by knowing * the followings: * - input unit * - locale * - usage *

* DESIGN: * {@code UnitRouter} uses internally {@code ComplexUnitConverter} in order to convert the input * units to the desired complex units and to check the limit too. */ public class UnitsRouter { // List of possible output units. TODO: converterPreferences_ now also has // this data available. Maybe drop outputUnits_ and have getOutputUnits // construct a the list from data in converterPreferences_ instead? private ArrayList outputUnits_ = new ArrayList<>(); private ArrayList converterPreferences_ = new ArrayList<>(); public UnitsRouter(String inputUnitIdentifier, ULocale locale, String usage) { this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), locale, usage); } public UnitsRouter(MeasureUnitImpl inputUnit, ULocale locale, String usage) { // TODO: do we want to pass in ConversionRates and UnitPreferences instead? // of loading in each UnitsRouter instance? (Or make global?) UnitsData data = new UnitsData(); String category = data.getCategory(inputUnit); UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, locale); for (int i = 0; i < unitPreferences.length; ++i) { UnitPreferences.UnitPreference preference = unitPreferences[i]; MeasureUnitImpl complexTargetUnitImpl = MeasureUnitImpl.UnitsParser.parseForIdentifier(preference.getUnit()); String precision = preference.getSkeleton(); // For now, we only have "precision-increment" in Units Preferences skeleton. // Therefore, we check if the skeleton starts with "precision-increment" and force the program to // fail otherwise. // NOTE: // It is allowed to have an empty precision. if (!precision.isEmpty() && !precision.startsWith("precision-increment")) { throw new AssertionError("Only `precision-increment` is allowed"); } outputUnits_.add(complexTargetUnitImpl.build()); converterPreferences_.add(new ConverterPreference(inputUnit, complexTargetUnitImpl, preference.getGeq(), precision, data.getConversionRates())); } } /** If micros.rounder is a BogusRounder, this function replaces it with a valid one. */ public RouteResult route(BigDecimal quantity, MicroProps micros) { Precision rounder = micros == null ? null : micros.rounder; ConverterPreference converterPreference = null; for (ConverterPreference itr : converterPreferences_) { converterPreference = itr; if (converterPreference.converter.greaterThanOrEqual(quantity.abs(), converterPreference.limit)) { break; } } assert converterPreference != null; assert converterPreference.precision != null; // Set up the rounder for this preference's precision if (rounder != null && rounder instanceof Precision.BogusRounder) { Precision.BogusRounder bogus = (Precision.BogusRounder)rounder; if (converterPreference.precision.length() > 0) { rounder = bogus.into(parseSkeletonToPrecision(converterPreference.precision)); } else { // We use the same rounding mode as COMPACT notation: known to be a // human-friendly rounding mode: integers, but add a decimal digit // as needed to ensure we have at least 2 significant digits. rounder = bogus.into(Precision.integer().withMinDigits(2)); } } if (micros != null) { micros.rounder = rounder; } return new RouteResult( converterPreference.converter.convert(quantity, rounder), converterPreference.targetUnit ); } private static Precision parseSkeletonToPrecision(String precisionSkeleton) { final String kSkeletonPrefix = "precision-increment/"; if (!precisionSkeleton.startsWith(kSkeletonPrefix)) { throw new IllegalIcuArgumentException("precisionSkeleton is only precision-increment"); } // TODO(icu-units#104): the C++ code uses a more sophisticated // parseIncrementOption which supports "withMinFraction" - e.g. // "precision-increment/0.5". Test with a unit preference that uses // this, and fix Java. String incrementValue = precisionSkeleton.substring(kSkeletonPrefix.length()); return Precision.increment(new BigDecimal(incrementValue)); } /** * Returns the list of possible output units, i.e. the full set of * preferences, for the localized, usage-specific unit preferences. *

* The returned pointer should be valid for the lifetime of the * UnitsRouter instance. */ public List getOutputUnits() { return this.outputUnits_; } /** * Contains the complex unit converter and the limit which representing the smallest value that the * converter should accept. For example, if the converter is converting to {@code foot+inch} and the * limit equals 3.0, thus means the converter should not convert to a value less than {@code 3.0 feet}. *

* NOTE: * if the limit doest not has a value (i.e. {@code std::numeric_limits::lowest()}), * this mean there is no limit for the converter. */ public static class ConverterPreference { // The output unit for this ConverterPreference. This may be a MIXED unit - // for example: "yard-and-foot-and-inch". final MeasureUnitImpl targetUnit; final ComplexUnitsConverter converter; final BigDecimal limit; final String precision; // In case there is no limit, the limit will be -inf. public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl targetUnit, String precision, ConversionRates conversionRates) { this(source, targetUnit, BigDecimal.valueOf(Double.MIN_VALUE), precision, conversionRates); } public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl targetUnit, BigDecimal limit, String precision, ConversionRates conversionRates) { this.converter = new ComplexUnitsConverter(source, targetUnit, conversionRates); this.limit = limit; this.precision = precision; this.targetUnit = targetUnit; } } public class RouteResult { public final ComplexUnitsConverter.ComplexConverterResult complexConverterResult; // The output unit for this RouteResult. This may be a MIXED unit - for // example: "yard-and-foot-and-inch", for which `measures` will have three // elements. public final MeasureUnitImpl outputUnit; RouteResult(ComplexUnitsConverter.ComplexConverterResult complexConverterResult, MeasureUnitImpl outputUnit) { this.complexConverterResult = complexConverterResult; this.outputUnit = outputUnit; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy