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 // TODO: revisit if the spec dictates prefix sort order - it doesn't 126 // currently. For now we're sorting binary prefixes before SI prefixes, 127 // as per ICU4C's enum values order. 128 if (this.getPrefix().getBase() < other.getPrefix().getBase()) { 129 return 1; 130 } 131 if (this.getPrefix().getBase() > other.getPrefix().getBase()) { 132 return -1; 133 } 134 if (this.getPrefix().getPower() < other.getPrefix().getPower()) { 135 return -1; 136 } 137 if (this.getPrefix().getPower() > other.getPrefix().getPower()) { 138 return 1; 139 } 140 return 0; 141 } 142 143 /** 144 * Checks whether this SingleUnitImpl is compatible with another for the purpose of coalescing. 145 * <p> 146 * Units with the same base unit and SI or binary prefix should match, except that they must also 147 * have the same dimensionality sign, such that we don't merge numerator and denominator. 148 */ isCompatibleWith(SingleUnitImpl other)149 boolean isCompatibleWith(SingleUnitImpl other) { 150 return (compareTo(other) == 0); 151 } 152 getSimpleUnitID()153 public String getSimpleUnitID() { 154 return simpleUnitID; 155 } 156 setSimpleUnit(int simpleUnitIndex, String[] simpleUnits)157 public void setSimpleUnit(int simpleUnitIndex, String[] simpleUnits) { 158 this.index = simpleUnitIndex; 159 this.simpleUnitID = simpleUnits[simpleUnitIndex]; 160 } 161 getDimensionality()162 public int getDimensionality() { 163 return dimensionality; 164 } 165 setDimensionality(int dimensionality)166 public void setDimensionality(int dimensionality) { 167 this.dimensionality = dimensionality; 168 } 169 getPrefix()170 public MeasureUnit.MeasurePrefix getPrefix() { 171 return unitPrefix; 172 } 173 setPrefix(MeasureUnit.MeasurePrefix unitPrefix)174 public void setPrefix(MeasureUnit.MeasurePrefix unitPrefix) { 175 this.unitPrefix = unitPrefix; 176 } 177 178 // TODO: unused? Delete? getIndex()179 public int getIndex() { 180 return index; 181 } 182 183 } 184