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