• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2020 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 package com.ibm.icu.impl.units;
5 
6 import com.ibm.icu.util.MeasureUnit;
7 
8 // TODO: revisit documentation in this file. E.g. we don't do dimensionless
9 // units in Java? We use null instead.
10 
11 /**
12  * A class representing a single unit (optional SI or binary prefix, and dimensionality).
13  */
14 public class SingleUnitImpl {
15     /**
16      * Simple unit index, unique for every simple unit, -1 for the dimensionless
17      * unit. This is an index into a string list in unit.txt {ConversionUnits}.
18      * <p>
19      * The default value is -1, meaning the dimensionless unit:
20      * isDimensionless() will return true, until index is changed.
21      */
22     private int index = -1;
23     /**
24      * SimpleUnit is the simplest form of a Unit. For example, for "square-millimeter", the simple unit would be "meter"Ò
25      * <p>
26      * The default value is "", meaning the dimensionless unit:
27      * isDimensionless() will return true, until index is changed.
28      */
29     private String simpleUnitID = "";
30     /**
31      * Determine the power of the `SingleUnit`. For example, for "square-meter", the dimensionality will be `2`.
32      * <p>
33      * NOTE:
34      * Default dimensionality is 1.
35      */
36     private int dimensionality = 1;
37     /**
38      * SI or binary prefix.
39      */
40     private MeasureUnit.MeasurePrefix unitPrefix = MeasureUnit.MeasurePrefix.ONE;
41 
copy()42     public SingleUnitImpl copy() {
43         SingleUnitImpl result = new SingleUnitImpl();
44         result.index = this.index;
45         result.dimensionality = this.dimensionality;
46         result.simpleUnitID = this.simpleUnitID;
47         result.unitPrefix = this.unitPrefix;
48 
49         return result;
50     }
51 
build()52     public MeasureUnit build() {
53         MeasureUnitImpl measureUnit = new MeasureUnitImpl(this);
54         return measureUnit.build();
55     }
56 
57     /**
58      * Generates a neutral identifier string for a single unit which means we do not include the dimension signal.
59      */
getNeutralIdentifier()60     public String getNeutralIdentifier() {
61         StringBuilder result = new StringBuilder();
62         int absPower = Math.abs(this.getDimensionality());
63 
64         assert absPower > 0 : "this function does not support the dimensionless single units";
65 
66         if (absPower == 1) {
67             // no-op
68         } else if (absPower == 2) {
69             result.append("square-");
70         } else if (absPower == 3) {
71             result.append("cubic-");
72         } else if (absPower <= 15) {
73             result.append("pow");
74             result.append(absPower);
75             result.append('-');
76         } else {
77             throw new IllegalArgumentException("Unit Identifier Syntax Error");
78         }
79 
80         result.append(this.getPrefix().getIdentifier());
81         result.append(this.getSimpleUnitID());
82 
83         return result.toString();
84     }
85 
86     /**
87      * Compare this SingleUnitImpl to another SingleUnitImpl for the sake of
88      * sorting and coalescing.
89      * <p>
90      * Sort order of units is specified by UTS #35
91      * (https://unicode.org/reports/tr35/tr35-info.html#Unit_Identifier_Normalization).
92      * <p>
93      * Takes the sign of dimensionality into account, but not the absolute
94      * value: per-meter is not considered the same as meter, but meter is
95      * considered the same as square-meter.
96      * <p>
97      * The dimensionless unit generally does not get compared, but if it did, it
98      * would sort before other units by virtue of index being < 0 and
99      * dimensionality not being negative.
100      */
compareTo(SingleUnitImpl other)101     int compareTo(SingleUnitImpl other) {
102         if (dimensionality < 0 && other.dimensionality > 0) {
103             // Positive dimensions first
104             return 1;
105         }
106         if (dimensionality > 0 && other.dimensionality < 0) {
107             return -1;
108         }
109         // Sort by official quantity order
110         int thisCategoryIndex = UnitsData.getCategoryIndexOfSimpleUnit(index);
111         int otherCategoryIndex = UnitsData.getCategoryIndexOfSimpleUnit(other.index);
112         if (thisCategoryIndex < otherCategoryIndex) {
113             return -1;
114         }
115         if (thisCategoryIndex > otherCategoryIndex) {
116             return 1;
117         }
118         // If quantity order didn't help, then we go by index.
119         if (index < other.index) {
120             return -1;
121         }
122         if (index > other.index) {
123             return 1;
124         }
125 
126         // When comparing binary prefixes vs SI prefixes, instead of comparing the actual values, we can
127         // multiply the binary prefix power by 3 and compare the powers. if they are equal, we can can
128         // compare the bases.
129         // NOTE: this methodology will fail if the binary prefix more than or equal 98.
130         int unitBase = this.unitPrefix.getBase();
131         int otherUnitBase = other.unitPrefix.getBase();
132         // Values for comparison purposes only.
133         int unitPowerComp =
134                 unitBase == 1024 /* Binary Prefix */ ? this.unitPrefix.getPower() * 3
135                         : this.unitPrefix.getPower();
136         int otherUnitPowerComp =
137                 otherUnitBase == 1024 /* Binary Prefix */ ? other.unitPrefix.getPower() * 3
138                         : other.unitPrefix.getPower();
139 
140         if (unitPowerComp < otherUnitPowerComp) {
141             return 1;
142         }
143         if (unitPowerComp > otherUnitPowerComp) {
144             return -1;
145         }
146 
147         if (unitBase < otherUnitBase) {
148             return 1;
149         }
150         if (unitBase > otherUnitBase) {
151             return -1;
152         }
153 
154         return 0;
155     }
156 
157     /**
158      * Checks whether this SingleUnitImpl is compatible with another for the purpose of coalescing.
159      * <p>
160      * Units with the same base unit and SI or binary prefix should match, except that they must also
161      * have the same dimensionality sign, such that we don't merge numerator and denominator.
162      */
isCompatibleWith(SingleUnitImpl other)163     boolean isCompatibleWith(SingleUnitImpl other) {
164         return (compareTo(other) == 0);
165     }
166 
getSimpleUnitID()167     public String getSimpleUnitID() {
168         return simpleUnitID;
169     }
170 
setSimpleUnit(int simpleUnitIndex, String[] simpleUnits)171     public void setSimpleUnit(int simpleUnitIndex, String[] simpleUnits) {
172         this.index = simpleUnitIndex;
173         this.simpleUnitID = simpleUnits[simpleUnitIndex];
174     }
175 
getDimensionality()176     public int getDimensionality() {
177         return dimensionality;
178     }
179 
setDimensionality(int dimensionality)180     public void setDimensionality(int dimensionality) {
181         this.dimensionality = dimensionality;
182     }
183 
getPrefix()184     public MeasureUnit.MeasurePrefix getPrefix() {
185         return unitPrefix;
186     }
187 
setPrefix(MeasureUnit.MeasurePrefix unitPrefix)188     public void setPrefix(MeasureUnit.MeasurePrefix unitPrefix) {
189         this.unitPrefix = unitPrefix;
190     }
191 
192     // TODO: unused? Delete?
getIndex()193     public int getIndex() {
194         return index;
195     }
196 
197 }
198