1 // © 2020 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 #include "unicode/utypes.h"
5 
6 #if !UCONFIG_NO_FORMATTING
7 #ifndef __UNITS_ROUTER_H__
8 #define __UNITS_ROUTER_H__
9 
10 #include <limits>
11 
12 #include "cmemory.h"
13 #include "measunit_impl.h"
14 #include "unicode/measunit.h"
15 #include "unicode/stringpiece.h"
16 #include "unicode/uobject.h"
17 #include "units_complexconverter.h"
18 #include "units_data.h"
19 
20 U_NAMESPACE_BEGIN
21 
22 // Forward declarations
23 class Measure;
24 namespace number {
25 class Precision;
26 }
27 
28 namespace units {
29 
30 struct RouteResult : UMemory {
31     // A list of measures: a single measure for single units, multiple measures
32     // for mixed units.
33     //
34     // TODO(icu-units/icu#21): figure out the right mixed unit API.
35     MaybeStackVector<Measure> measures;
36 
37     // The output unit for this RouteResult. This may be a MIXED unit - for
38     // example: "yard-and-foot-and-inch", for which `measures` will have three
39     // elements.
40     MeasureUnitImpl outputUnit;
41 
RouteResultRouteResult42     RouteResult(MaybeStackVector<Measure> measures, MeasureUnitImpl outputUnit)
43         : measures(std::move(measures)), outputUnit(std::move(outputUnit)) {}
44 };
45 
46 /**
47  * Contains the complex unit converter and the limit which representing the smallest value that the
48  * converter should accept. For example, if the converter is converting to `foot+inch` and the limit
49  * equals 3.0, thus means the converter should not convert to a value less than `3.0 feet`.
50  *
51  * NOTE:
52  *    if the limit doest not has a value `i.e. (std::numeric_limits<double>::lowest())`, this mean there
53  *    is no limit for the converter.
54  */
55 struct ConverterPreference : UMemory {
56     ComplexUnitsConverter converter;
57     double limit;
58     UnicodeString precision;
59 
60     // The output unit for this ConverterPreference. This may be a MIXED unit -
61     // for example: "yard-and-foot-and-inch".
62     MeasureUnitImpl targetUnit;
63 
64     // In case there is no limit, the limit will be -inf.
ConverterPreferenceConverterPreference65     ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
66                         UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status)
67         : ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), precision,
68                               ratesInfo, status) {}
69 
ConverterPreferenceConverterPreference70     ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
71                         double limit, UnicodeString precision, const ConversionRates &ratesInfo,
72                         UErrorCode &status)
73         : converter(source, complexTarget, ratesInfo, status), limit(limit),
74           precision(std::move(precision)), targetUnit(complexTarget.copy(status)) {}
75 };
76 
77 } // namespace units
78 
79 // Export explicit template instantiations of MaybeStackArray, MemoryPool and
80 // MaybeStackVector. This is required when building DLLs for Windows. (See
81 // datefmt.h, collationiterator.h, erarules.h and others for similar examples.)
82 //
83 // Note: These need to be outside of the units namespace, or Clang will generate
84 // a compile error.
85 #if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
86 template class U_I18N_API MaybeStackArray<units::ConverterPreference*, 8>;
87 template class U_I18N_API MemoryPool<units::ConverterPreference, 8>;
88 template class U_I18N_API MaybeStackVector<units::ConverterPreference, 8>;
89 #endif
90 
91 namespace units {
92 
93 /**
94  * `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to
95  * one of the complex units based on the limits.
96  * For example:
97  *    if the input is `meter` and the output as following
98  *    {`foot+inch`, limit: 3.0}
99  *    {`inch`     , limit: no value (-inf)}
100  *    Thus means if the input in `meter` is greater than or equal to `3.0 feet`, the output will be in
101  *    `foot+inch`, otherwise, the output will be in `inch`.
102  *
103  * NOTE:
104  *    the output units and the their limits MUST BE in order, for example, if the output units, from the
105  *    previous example, are the following:
106  *        {`inch`     , limit: no value (-inf)}
107  *        {`foot+inch`, limit: 3.0}
108  *     IN THIS CASE THE OUTPUT WILL BE ALWAYS IN `inch`.
109  *
110  * NOTE:
111  *    the output units  and their limits will be extracted from the units preferences database by knowing
112  *    the followings:
113  *        - input unit
114  *        - locale
115  *        - usage
116  *
117  * DESIGN:
118  *    `UnitRouter` uses internally `ComplexUnitConverter` in order to convert the input units to the
119  *    desired complex units and to check the limit too.
120  */
121 class U_I18N_API UnitsRouter {
122   public:
123     UnitsRouter(StringPiece inputUnitIdentifier, StringPiece locale, StringPiece usage,
124                 UErrorCode &status);
125     UnitsRouter(const MeasureUnit &inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
126 
127     /**
128      * Performs locale and usage sensitive unit conversion.
129      * @param quantity The quantity to convert, expressed in terms of inputUnit.
130      * @param rounder If not null, this RoundingImpl will be used to do rounding
131      *     on the converted value. If the rounder lacks an fPrecision, the
132      *     rounder will be modified to use the preferred precision for the usage
133      *     and locale preference, alternatively with the default precision.
134      * @param status Receives status.
135      */
136     RouteResult route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const;
137 
138     /**
139      * Returns the list of possible output units, i.e. the full set of
140      * preferences, for the localized, usage-specific unit preferences.
141      *
142      * The returned pointer should be valid for the lifetime of the
143      * UnitsRouter instance.
144      */
145     const MaybeStackVector<MeasureUnit> *getOutputUnits() const;
146 
147   private:
148     // List of possible output units. TODO: converterPreferences_ now also has
149     // this data available. Maybe drop outputUnits_ and have getOutputUnits
150     // construct a the list from data in converterPreferences_ instead?
151     MaybeStackVector<MeasureUnit> outputUnits_;
152 
153     MaybeStackVector<ConverterPreference> converterPreferences_;
154 
155     static number::Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
156                                                       UErrorCode &status);
157 
158     void init(const MeasureUnit &inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
159 };
160 
161 } // namespace units
162 U_NAMESPACE_END
163 
164 #endif //__UNITS_ROUTER_H__
165 
166 #endif /* #if !UCONFIG_NO_FORMATTING */
167