summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/units_router.h
blob: 978fdf91fd5b4e7d7c1b880e9c8263c92a7b2e87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING
#ifndef __UNITS_ROUTER_H__
#define __UNITS_ROUTER_H__

#include <limits>

#include "cmemory.h"
#include "measunit_impl.h"
#include "unicode/locid.h"
#include "unicode/measunit.h"
#include "unicode/stringpiece.h"
#include "unicode/uobject.h"
#include "units_complexconverter.h"
#include "units_data.h"

U_NAMESPACE_BEGIN

// Forward declarations
class Measure;
namespace number {
class Precision;
}

namespace units {

struct RouteResult : UMemory {
    // A list of measures: a single measure for single units, multiple measures
    // for mixed units.
    MaybeStackVector<Measure> measures;

    // 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.
    MeasureUnitImpl outputUnit;

    RouteResult(MaybeStackVector<Measure> measures, MeasureUnitImpl outputUnit)
        : measures(std::move(measures)), outputUnit(std::move(outputUnit)) {}
};

/**
 * 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 `foot+inch` and the limit
 * equals 3.0, thus means the converter should not convert to a value less than `3.0 feet`.
 *
 * NOTE:
 *    if the limit doest not has a value `i.e. (std::numeric_limits<double>::lowest())`, this mean there
 *    is no limit for the converter.
 */
struct ConverterPreference : UMemory {
    ComplexUnitsConverter converter;
    double limit;
    UnicodeString precision;

    // The output unit for this ConverterPreference. This may be a MIXED unit -
    // for example: "yard-and-foot-and-inch".
    MeasureUnitImpl targetUnit;

    // In case there is no limit, the limit will be -inf.
    ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
                        UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status)
        : ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), precision,
                              ratesInfo, status) {}

    ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
                        double limit, UnicodeString precision, const ConversionRates &ratesInfo,
                        UErrorCode &status)
        : converter(source, complexTarget, ratesInfo, status), limit(limit),
          precision(std::move(precision)), targetUnit(complexTarget.copy(status)) {}
};

} // namespace units

// Export explicit template instantiations of MaybeStackArray, MemoryPool and
// MaybeStackVector. This is required when building DLLs for Windows. (See
// datefmt.h, collationiterator.h, erarules.h and others for similar examples.)
//
// Note: These need to be outside of the units namespace, or Clang will generate
// a compile error.
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
template class U_I18N_API MaybeStackArray<units::ConverterPreference*, 8>;
template class U_I18N_API MemoryPool<units::ConverterPreference, 8>;
template class U_I18N_API MaybeStackVector<units::ConverterPreference, 8>;
#endif

namespace units {

/**
 * `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to
 * one of the complex units based on the limits.
 * For example:
 *    if the input is `meter` and the output as following
 *    {`foot+inch`, limit: 3.0}
 *    {`inch`     , limit: no value (-inf)}
 *    Thus means if the input in `meter` is greater than or equal to `3.0 feet`, the output will be in
 *    `foot+inch`, otherwise, the output will be in `inch`.
 *
 * NOTE:
 *    the output units and the their limits MUST BE in order, for example, if the output units, from the
 *    previous example, are the following:
 *        {`inch`     , limit: no value (-inf)}
 *        {`foot+inch`, limit: 3.0}
 *     IN THIS CASE THE OUTPUT WILL BE ALWAYS IN `inch`.
 *
 * NOTE:
 *    the output units  and their limits will be extracted from the units preferences database by knowing
 *    the following:
 *        - input unit
 *        - locale
 *        - usage
 *
 * DESIGN:
 *    `UnitRouter` uses internally `ComplexUnitConverter` in order to convert the input units to the
 *    desired complex units and to check the limit too.
 */
class U_I18N_API UnitsRouter {
  public:
    UnitsRouter(StringPiece inputUnitIdentifier, const Locale &locale, StringPiece usage,
                UErrorCode &status);
    UnitsRouter(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
                UErrorCode &status);

    /**
     * Performs locale and usage sensitive unit conversion.
     * @param quantity The quantity to convert, expressed in terms of inputUnit.
     * @param rounder If not null, this RoundingImpl will be used to do rounding
     *     on the converted value. If the rounder lacks an fPrecision, the
     *     rounder will be modified to use the preferred precision for the usage
     *     and locale preference, alternatively with the default precision.
     * @param status Receives status.
     */
    RouteResult route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const;

    /**
     * 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.
     */
    const MaybeStackVector<MeasureUnit> *getOutputUnits() const;

  private:
    // 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?
    MaybeStackVector<MeasureUnit> outputUnits_;

    MaybeStackVector<ConverterPreference> converterPreferences_;

    static number::Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
                                                      UErrorCode &status);

    void init(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage, UErrorCode &status);
};

} // namespace units
U_NAMESPACE_END

#endif //__UNITS_ROUTER_H__

#endif /* #if !UCONFIG_NO_FORMATTING */